From 2ce70949c19648bd0801f56898d13ad3a9b66444 Mon Sep 17 00:00:00 2001 From: Berkay Bayar <77205615+bberka@users.noreply.github.com> Date: Sun, 6 Apr 2025 23:35:26 +0300 Subject: [PATCH 01/47] Update dotnet-dev-build.yml --- .github/workflows/dotnet-dev-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-dev-build.yml b/.github/workflows/dotnet-dev-build.yml index f03a5c5b..e7772638 100644 --- a/.github/workflows/dotnet-dev-build.yml +++ b/.github/workflows/dotnet-dev-build.yml @@ -1,4 +1,4 @@ -name: Build on Push +name: Build Dev on Push on: push: From 4ea0fc8ddb63f4efb9d1281efe6121b2d01e1ab4 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 00:19:49 +0300 Subject: [PATCH 02/47] fix: statistics calculation corrected --- src/Modmail.NET/Jobs/StatisticsCalculatorJob.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Modmail.NET/Jobs/StatisticsCalculatorJob.cs b/src/Modmail.NET/Jobs/StatisticsCalculatorJob.cs index 3c1e2a33..cc597495 100644 --- a/src/Modmail.NET/Jobs/StatisticsCalculatorJob.cs +++ b/src/Modmail.NET/Jobs/StatisticsCalculatorJob.cs @@ -52,7 +52,7 @@ public override async Task Execute() { private static async Task GetAvgResponseTimeSeconds(ModmailDbContext dbContext, DateTime statDate) { try { return await dbContext.TicketMessages - .Where(x => x.RegisterDateUtc < statDate) + .Where(x => x.RegisterDateUtc > statDate) .Select(x => new { x.TicketId, x.RegisterDateUtc, @@ -89,7 +89,7 @@ public override async Task Execute() { private static async Task GetAvgTicketsClosedPerDay(ModmailDbContext dbContext, DateTime statDate) { try { return await dbContext.Tickets - .Where(x => x.RegisterDateUtc < statDate) + .Where(x => x.RegisterDateUtc > statDate) .Where(x => x.ClosedDateUtc.HasValue) .GroupBy(x => x.ClosedDateUtc.Value.Date) .Select(group => group.Count()) @@ -105,7 +105,7 @@ public override async Task Execute() { private static async Task GetAvgTicketsOpenedPerDay(ModmailDbContext dbContext, DateTime statDate) { try { return await dbContext.Tickets - .Where(x => x.RegisterDateUtc < statDate) + .Where(x => x.RegisterDateUtc > statDate) .GroupBy(x => x.RegisterDateUtc.Date) .Select(group => group.Count()) .DefaultIfEmpty() @@ -120,7 +120,7 @@ public override async Task Execute() { private static async Task GetAvgTicketClosedSeconds(ModmailDbContext dbContext, DateTime statDate) { try { return await dbContext.Tickets - .Where(x => x.RegisterDateUtc < statDate) + .Where(x => x.RegisterDateUtc > statDate) .Where(x => x.ClosedDateUtc.HasValue) .Select(x => EF.Functions.DateDiffSecond(x.RegisterDateUtc, x.ClosedDateUtc.Value)) .DefaultIfEmpty() @@ -135,7 +135,7 @@ public override async Task Execute() { private static async Task GetFastestClosedTicketSeconds(ModmailDbContext dbContext, DateTime statDate) { try { return await dbContext.Tickets - .Where(x => x.RegisterDateUtc < statDate) + .Where(x => x.RegisterDateUtc > statDate) .Where(x => x.ClosedDateUtc.HasValue) .Select(x => EF.Functions.DateDiffSecond(x.RegisterDateUtc, x.ClosedDateUtc.Value)) .DefaultIfEmpty() @@ -150,7 +150,7 @@ public override async Task Execute() { private static async Task GetSlowestClosedTicketSeconds(ModmailDbContext dbContext, DateTime statDate) { try { return await dbContext.Tickets - .Where(x => x.RegisterDateUtc < statDate) + .Where(x => x.RegisterDateUtc > statDate) .Where(x => x.ClosedDateUtc.HasValue) .Select(x => EF.Functions.DateDiffSecond(x.RegisterDateUtc, x.ClosedDateUtc.Value)) .DefaultIfEmpty() From 56d9ea687aa7cf9339428f258f8522ea739b4f72 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 00:21:26 +0300 Subject: [PATCH 03/47] fix: discord bot intents --- src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs b/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs index 44066dad..362f30cd 100644 --- a/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs +++ b/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs @@ -18,7 +18,7 @@ public static void Configure(WebApplicationBuilder builder) { | DiscordIntents.GuildMessages | DiscordIntents.GuildMembers | DiscordIntents.DirectMessages - | DiscordIntents.GuildMessages + | DiscordIntents.GuildMessageReactions | DiscordIntents.DirectMessageReactions); builder.Services.AddCommandsExtension((_, extension) => { From d642ddb022d438cc946a1ad3b266609a3eba9bfa Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 00:22:03 +0300 Subject: [PATCH 04/47] chore: removed duplicated bot dependency configuration --- src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs b/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs index 362f30cd..fa135d5d 100644 --- a/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs +++ b/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs @@ -54,7 +54,6 @@ public static void Configure(WebApplicationBuilder builder) { eventHandlingBuilder.HandleGuildMemberAdded(ModmailEventHandlers.OnGuildMemberAdded); eventHandlingBuilder.HandleGuildMemberRemoved(ModmailEventHandlers.OnGuildMemberRemoved); eventHandlingBuilder.HandleGuildBanAdded(ModmailEventHandlers.OnGuildBanAdded); - eventHandlingBuilder.HandleGuildBanAdded(ModmailEventHandlers.OnGuildBanAdded); eventHandlingBuilder.HandleGuildBanRemoved(ModmailEventHandlers.OnGuildBanRemoved); eventHandlingBuilder.HandleUserUpdated(ModmailEventHandlers.OnUserUpdated); From 9187c0d0096e78663e4959effb12d10120c696f6 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 01:04:53 +0300 Subject: [PATCH 05/47] feat: Converted ProcessedReactionDiscordEmoji string to unicode --- .../Features/Ticket/Handlers/ProcessCreateNewTicketHandler.cs | 2 +- .../Features/Ticket/Handlers/ProcessModSendMessageHandler.cs | 2 +- .../Features/Ticket/Handlers/ProcessUserSentMessageHandler.cs | 2 +- src/Modmail.NET/Static/Const.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateNewTicketHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateNewTicketHandler.cs index e1398bb2..c819756d 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateNewTicketHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateNewTicketHandler.cs @@ -109,6 +109,6 @@ public async Task Handle(ProcessCreateNewTicketCommand request, CancellationToke var logChannel = await _sender.Send(new GetDiscordLogChannelQuery(), cancellationToken); await logChannel.SendMessageAsync(newTicketCreatedLog); - await request.Message.CreateReactionAsync(DiscordEmoji.FromName(_bot.Client, Const.ProcessedReactionDiscordEmojiString, false)); + await request.Message.CreateReactionAsync(DiscordEmoji.FromUnicode(Const.ProcessedReactionDiscordEmojiUnicode)); } } \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessModSendMessageHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessModSendMessageHandler.cs index 034c7443..60ceb594 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessModSendMessageHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessModSendMessageHandler.cs @@ -57,6 +57,6 @@ public async Task Handle(ProcessModSendMessageCommand request, CancellationToken var affected = await _dbContext.SaveChangesAsync(cancellationToken); if (affected == 0) throw new DbInternalException(); - await request.Message.CreateReactionAsync(DiscordEmoji.FromName(_bot.Client, Const.ProcessedReactionDiscordEmojiString, false)); + await request.Message.CreateReactionAsync(DiscordEmoji.FromUnicode(Const.ProcessedReactionDiscordEmojiUnicode)); } } \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessUserSentMessageHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessUserSentMessageHandler.cs index 5f65cc78..aeb16f93 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessUserSentMessageHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessUserSentMessageHandler.cs @@ -49,6 +49,6 @@ public async Task Handle(ProcessUserSentMessageCommand request, CancellationToke var affected = await _dbContext.SaveChangesAsync(cancellationToken); if (affected == 0) throw new DbInternalException(); - await request.Message.CreateReactionAsync(DiscordEmoji.FromName(_bot.Client, Const.ProcessedReactionDiscordEmojiString, false)); + await request.Message.CreateReactionAsync(DiscordEmoji.FromUnicode(Const.ProcessedReactionDiscordEmojiUnicode)); } } \ No newline at end of file diff --git a/src/Modmail.NET/Static/Const.cs b/src/Modmail.NET/Static/Const.cs index dbd2b937..a5da5b7c 100644 --- a/src/Modmail.NET/Static/Const.cs +++ b/src/Modmail.NET/Static/Const.cs @@ -20,6 +20,6 @@ public static class Const public const string ThemeCookieName = "Modmail.NET.Theme"; public const string AttachmentDownloadDirectory = "AttachmentDownloads"; public const int HttpClientDownloadTimeoutSeconds = 90; - public const string ProcessedReactionDiscordEmojiString = ":white_check_mark:"; + public const string ProcessedReactionDiscordEmojiUnicode = "✅"; public static readonly DiscordActivity DiscordActivity = new(LangProvider.This.GetTranslation(LangKeys.ModerationConcerns), DiscordActivityType.ListeningTo); } \ No newline at end of file From 98d3b8a4002d9016ea5508d55e7632c431461529 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 01:05:07 +0300 Subject: [PATCH 06/47] feat: Enhance user engagement with mirrored reactions - Reactions added by moderators in ticket channels are now automatically mirrored to the corresponding user's direct messages, and vice versa, creating a more interactive communication experience. - The default "processed" reaction is excluded from mirroring. --- .../Dependency/DiscordBotDependency.cs | 14 +- src/Modmail.NET/ModmailEventHandlers.cs | 127 ++++++++++++++++++ 2 files changed, 137 insertions(+), 4 deletions(-) diff --git a/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs b/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs index fa135d5d..b8f10637 100644 --- a/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs +++ b/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs @@ -47,6 +47,16 @@ public static void Configure(WebApplicationBuilder builder) { eventHandlingBuilder.HandleMessageCreated(ModmailEventHandlers.OnMessageCreated); eventHandlingBuilder.HandleChannelDeleted(ModmailEventHandlers.OnChannelDeleted); + eventHandlingBuilder.HandleMessageReactionAdded(ModmailEventHandlers.OnMessageReactionAdded); + eventHandlingBuilder.HandleMessageReactionRemoved(ModmailEventHandlers.OnMessageReactionRemoved); + + //TODO: investigate the need to implement handling of other reaction events + // eventHandlingBuilder.HandleMessageReactionsCleared(); + // eventHandlingBuilder.HandleMessageReactionRemovedEmoji(); + + eventHandlingBuilder.HandleMessageDeleted(ModmailEventHandlers.OnMessageDeleted); + eventHandlingBuilder.HandleMessageUpdated(ModmailEventHandlers.OnMessageUpdated); + eventHandlingBuilder.HandleInteractionCreated(ModmailEventHandlers.InteractionCreated); eventHandlingBuilder.HandleComponentInteractionCreated(ModmailEventHandlers.ComponentInteractionCreated); eventHandlingBuilder.HandleModalSubmitted(ModmailEventHandlers.ModalSubmitted); @@ -58,10 +68,6 @@ public static void Configure(WebApplicationBuilder builder) { eventHandlingBuilder.HandleUserUpdated(ModmailEventHandlers.OnUserUpdated); eventHandlingBuilder.HandleUserSettingsUpdated(ModmailEventHandlers.OnUserSettingsUpdated); - - eventHandlingBuilder.HandleMessageReactionAdded(ModmailEventHandlers.OnMessageReactionAdded); - eventHandlingBuilder.HandleMessageDeleted(ModmailEventHandlers.OnMessageDeleted); - eventHandlingBuilder.HandleMessageUpdated(ModmailEventHandlers.OnMessageUpdated); }); } } \ No newline at end of file diff --git a/src/Modmail.NET/ModmailEventHandlers.cs b/src/Modmail.NET/ModmailEventHandlers.cs index 18862de9..3af13a7c 100644 --- a/src/Modmail.NET/ModmailEventHandlers.cs +++ b/src/Modmail.NET/ModmailEventHandlers.cs @@ -2,10 +2,13 @@ using DSharpPlus.Entities; using DSharpPlus.Entities.AuditLogs; using DSharpPlus.EventArgs; +using DSharpPlus.Exceptions; using MediatR; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Modmail.NET.Abstract; using Modmail.NET.Aspects; +using Modmail.NET.Database; using Modmail.NET.Features.Ticket; using Modmail.NET.Features.UserInfo; using Modmail.NET.Models.Dto; @@ -44,7 +47,131 @@ public static async Task OnMessageDeleted(DiscordClient client, MessageDeletedEv public static async Task OnMessageReactionAdded(DiscordClient client, MessageReactionAddedEventArgs args) { var scope = client.ServiceProvider.CreateScope(); var sender = scope.ServiceProvider.GetRequiredService(); + var bot = scope.ServiceProvider.GetRequiredService(); await sender.Send(new UpdateDiscordUserCommand(args?.User)); + + if (args is not null) { + if (args.User.IsBot) return; + + if (args.Emoji.Name == Const.ProcessedReactionDiscordEmojiUnicode) return; + + var isPrivate = args.Channel.IsPrivate; + if (isPrivate) { + var isAuthorBot = args.Message.Author?.Id == client.CurrentUser.Id; + if (isAuthorBot) return; + + var dbContext = scope.ServiceProvider.GetRequiredService(); + var messageEntity = await dbContext.TicketMessages + .Where(x => x.SentByMod && x.BotMessageId == args.Message.Id) + .FirstOrDefaultAsync(); + if (messageEntity is null) return; + + var ticket = await dbContext.Tickets + .Where(x => !x.ClosedDateUtc.HasValue && x.OpenerUserId == args.User.Id && x.Id == messageEntity.TicketId && x.PrivateMessageChannelId == args.Channel.Id) + .FirstOrDefaultAsync(); + if (ticket is null) return; + + try { + var ticketChannel = await client.GetChannelAsync(ticket.ModMessageChannelId); + var message = await ticketChannel.GetMessageAsync(messageEntity.MessageDiscordId); + await message.CreateReactionAsync(args.Emoji); + } + catch (NotFoundException) { + //ignored + } + } + else { + var topic = args.Channel.Topic; + var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(topic); + if (ticketId == Guid.Empty) return; + + var dbContext = scope.ServiceProvider.GetRequiredService(); + + var messageEntity = await dbContext.TicketMessages + .Where(x => !x.SentByMod && x.BotMessageId == args.Message.Id && x.TicketId == ticketId) + .FirstOrDefaultAsync(); + if (messageEntity is null) return; + + var ticket = await dbContext.Tickets + .Where(x => !x.ClosedDateUtc.HasValue && x.Id == ticketId && x.ModMessageChannelId == args.Channel.Id) + .FirstOrDefaultAsync(); + if (ticket is null) return; + + try { + var pmChannel = await client.GetChannelAsync(ticket.PrivateMessageChannelId); + var message = await pmChannel.GetMessageAsync(messageEntity.MessageDiscordId); + await message.CreateReactionAsync(args.Emoji); + } + catch (NotFoundException) { + //ignored + } + } + } + } + + public static async Task OnMessageReactionRemoved(DiscordClient client, MessageReactionRemovedEventArgs args) { + var scope = client.ServiceProvider.CreateScope(); + var sender = scope.ServiceProvider.GetRequiredService(); + var bot = scope.ServiceProvider.GetRequiredService(); + await sender.Send(new UpdateDiscordUserCommand(args?.User)); + + if (args is not null) { + if (args.User.IsBot) return; + + if (args.Emoji.Name == Const.ProcessedReactionDiscordEmojiUnicode) return; + + var isPrivate = args.Channel.IsPrivate; + if (isPrivate) { + var isAuthorBot = args.Message.Author?.Id == client.CurrentUser.Id; + if (!isAuthorBot) return; + + var dbContext = scope.ServiceProvider.GetRequiredService(); + var messageEntity = await dbContext.TicketMessages + .Where(x => x.SentByMod && x.BotMessageId == args.Message.Id) + .FirstOrDefaultAsync(); + if (messageEntity is null) return; + + var ticket = await dbContext.Tickets + .Where(x => !x.ClosedDateUtc.HasValue && x.OpenerUserId == args.User.Id && x.Id == messageEntity.TicketId && x.PrivateMessageChannelId == args.Channel.Id) + .FirstOrDefaultAsync(); + if (ticket is null) return; + + try { + var ticketChannel = await client.GetChannelAsync(ticket.ModMessageChannelId); + var message = await ticketChannel.GetMessageAsync(messageEntity.MessageDiscordId); + await message.DeleteOwnReactionAsync(args.Emoji); + } + catch (NotFoundException) { + //ignored + } + } + else { + var topic = args.Channel.Topic; + var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(topic); + if (ticketId == Guid.Empty) return; + + var dbContext = scope.ServiceProvider.GetRequiredService(); + + var messageEntity = await dbContext.TicketMessages + .Where(x => !x.SentByMod && x.BotMessageId == args.Message.Id && x.TicketId == ticketId) + .FirstOrDefaultAsync(); + if (messageEntity is null) return; + + var ticket = await dbContext.Tickets + .Where(x => !x.ClosedDateUtc.HasValue && x.Id == ticketId && x.ModMessageChannelId == args.Channel.Id) + .FirstOrDefaultAsync(); + if (ticket is null) return; + + try { + var pmChannel = await client.GetChannelAsync(ticket.PrivateMessageChannelId); + var message = await pmChannel.GetMessageAsync(messageEntity.MessageDiscordId); + await message.DeleteOwnReactionAsync(args.Emoji); + } + catch (NotFoundException) { + //ignored + } + } + } } public static async Task OnMessageUpdated(DiscordClient client, MessageUpdatedEventArgs args) { From 3405c23866940d59a9e6cecf0ae9a0f01314899d Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 01:15:50 +0300 Subject: [PATCH 07/47] feat: Implement mirrored message deletion - When a user deletes a message in their DM, the corresponding mirrored message in the ticket channel is now automatically deleted by the bot. --- src/Modmail.NET/ModmailEventHandlers.cs | 53 +++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/Modmail.NET/ModmailEventHandlers.cs b/src/Modmail.NET/ModmailEventHandlers.cs index 3af13a7c..bb7946fe 100644 --- a/src/Modmail.NET/ModmailEventHandlers.cs +++ b/src/Modmail.NET/ModmailEventHandlers.cs @@ -42,6 +42,59 @@ public static async Task OnMessageDeleted(DiscordClient client, MessageDeletedEv var scope = client.ServiceProvider.CreateScope(); var sender = scope.ServiceProvider.GetRequiredService(); await sender.Send(new UpdateDiscordUserCommand(args?.Message.Author)); + + if (args is not null) { + if (args.Message.Author?.IsBot == true) return; + var isPrivate = args.Channel.IsPrivate; + if (isPrivate) { + // message deleted by user in dms + var dbContext = scope.ServiceProvider.GetRequiredService(); + var messageEntity = await dbContext.TicketMessages + .Where(x => !x.SentByMod && x.MessageDiscordId == args.Message.Id) + .FirstOrDefaultAsync(); + if (messageEntity is null) return; + + var ticket = await dbContext.Tickets + .Where(x => !x.ClosedDateUtc.HasValue && x.Id == messageEntity.TicketId && x.PrivateMessageChannelId == args.Channel.Id) + .FirstOrDefaultAsync(); + if (ticket is null) return; + + try { + var ticketChannel = await client.GetChannelAsync(ticket.ModMessageChannelId); + var message = await ticketChannel.GetMessageAsync(messageEntity.BotMessageId); + await message.DeleteAsync(); + } + catch (NotFoundException) { + //ignored + } + } + else { + var topic = args.Channel.Topic; + var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(topic); + if (ticketId == Guid.Empty) return; + // message deleted by mod or other admin in ticket channel + var dbContext = scope.ServiceProvider.GetRequiredService(); + + var messageEntity = await dbContext.TicketMessages + .Where(x => x.SentByMod && x.TicketId == ticketId && x.MessageDiscordId == args.Message.Id) + .FirstOrDefaultAsync(); + if (messageEntity is null) return; + + var ticket = await dbContext.Tickets + .Where(x => !x.ClosedDateUtc.HasValue && x.Id == ticketId && x.ModMessageChannelId == args.Channel.Id) + .FirstOrDefaultAsync(); + if (ticket is null) return; + + try { + var dmChannel = await client.GetChannelAsync(ticket.PrivateMessageChannelId); + var message = await dmChannel.GetMessageAsync(messageEntity.BotMessageId); + await message.DeleteAsync(); + } + catch (NotFoundException) { + //ignored + } + } + } } public static async Task OnMessageReactionAdded(DiscordClient client, MessageReactionAddedEventArgs args) { From 9ca8168f9871d076cd7100c3289dfcbdf224a3d8 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 13:07:10 +0300 Subject: [PATCH 08/47] feat: added new log messages - OnMessageReactionRemoved - OnMessageReactionAdded - OnMessageDeleted --- src/Modmail.NET/ModmailEventHandlers.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Modmail.NET/ModmailEventHandlers.cs b/src/Modmail.NET/ModmailEventHandlers.cs index bb7946fe..fae561c4 100644 --- a/src/Modmail.NET/ModmailEventHandlers.cs +++ b/src/Modmail.NET/ModmailEventHandlers.cs @@ -63,6 +63,7 @@ public static async Task OnMessageDeleted(DiscordClient client, MessageDeletedEv var ticketChannel = await client.GetChannelAsync(ticket.ModMessageChannelId); var message = await ticketChannel.GetMessageAsync(messageEntity.BotMessageId); await message.DeleteAsync(); + Log.Information("[OnMessageDeleted] Processed message deleted {ChannelId} {MessageId} {MessageAuthor}", ticketChannel.Id, message.Id, message.Author?.Id); } catch (NotFoundException) { //ignored @@ -89,6 +90,7 @@ public static async Task OnMessageDeleted(DiscordClient client, MessageDeletedEv var dmChannel = await client.GetChannelAsync(ticket.PrivateMessageChannelId); var message = await dmChannel.GetMessageAsync(messageEntity.BotMessageId); await message.DeleteAsync(); + Log.Information("[OnMessageDeleted] Processed message deleted {ChannelId} {MessageId} {MessageAuthor}", dmChannel.Id, message.Id, message.Author?.Id); } catch (NotFoundException) { //ignored @@ -128,6 +130,7 @@ public static async Task OnMessageReactionAdded(DiscordClient client, MessageRea var ticketChannel = await client.GetChannelAsync(ticket.ModMessageChannelId); var message = await ticketChannel.GetMessageAsync(messageEntity.MessageDiscordId); await message.CreateReactionAsync(args.Emoji); + Log.Information("[OnMessageReactionAdded] Processed reaction added {ChannelId} {MessageId} {MessageAuthor} {Emoji}", ticketChannel.Id, message.Id, message.Author?.Id, args.Emoji); } catch (NotFoundException) { //ignored @@ -154,6 +157,7 @@ public static async Task OnMessageReactionAdded(DiscordClient client, MessageRea var pmChannel = await client.GetChannelAsync(ticket.PrivateMessageChannelId); var message = await pmChannel.GetMessageAsync(messageEntity.MessageDiscordId); await message.CreateReactionAsync(args.Emoji); + Log.Information("[OnMessageReactionAdded] Processed reaction added {ChannelId} {MessageId} {MessageAuthor} {Emoji}", pmChannel.Id, message.Id, message.Author?.Id, args.Emoji); } catch (NotFoundException) { //ignored @@ -193,6 +197,7 @@ public static async Task OnMessageReactionRemoved(DiscordClient client, MessageR var ticketChannel = await client.GetChannelAsync(ticket.ModMessageChannelId); var message = await ticketChannel.GetMessageAsync(messageEntity.MessageDiscordId); await message.DeleteOwnReactionAsync(args.Emoji); + Log.Information("[OnMessageReactionRemoved] Processed reaction added {ChannelId} {MessageId} {MessageAuthor} {Emoji}", ticketChannel.Id, message.Id, message.Author?.Id, args.Emoji); } catch (NotFoundException) { //ignored @@ -219,6 +224,7 @@ public static async Task OnMessageReactionRemoved(DiscordClient client, MessageR var pmChannel = await client.GetChannelAsync(ticket.PrivateMessageChannelId); var message = await pmChannel.GetMessageAsync(messageEntity.MessageDiscordId); await message.DeleteOwnReactionAsync(args.Emoji); + Log.Information("[OnMessageReactionRemoved] Processed reaction added {ChannelId} {MessageId} {MessageAuthor} {Emoji}", pmChannel.Id, message.Id, message.Author?.Id, args.Emoji); } catch (NotFoundException) { //ignored From 31f1bfce26e4ec4c9d6a46edfbd778982e3f683d Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 16:04:58 +0300 Subject: [PATCH 09/47] feat: new lang keys --- src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json | 4 +++- src/Modmail.NET/Language/LangKeys.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json b/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json index e9b928f6..e0bf23f6 100644 --- a/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json +++ b/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json @@ -200,5 +200,7 @@ "ErrorNotFound": "Page you looking for doesnt exist or moved", "NotJoinedMainServer": "Bot is not added to main server", "TicketPriority": "Ticket Priority", - "Transcript": "Transcript" + "Transcript": "Transcript", + "MessageEdited": "Message Edited", + "Edited": "Edited" } diff --git a/src/Modmail.NET/Language/LangKeys.cs b/src/Modmail.NET/Language/LangKeys.cs index 6a8ee87c..ee7a791a 100644 --- a/src/Modmail.NET/Language/LangKeys.cs +++ b/src/Modmail.NET/Language/LangKeys.cs @@ -203,5 +203,7 @@ public enum LangKeys ErrorNotFound, NotJoinedMainServer, TicketPriority, - Transcript + Transcript, + MessageEdited, + Edited } \ No newline at end of file From 7ea76dede576c93881b8c43629bff28f55201c77 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 16:05:27 +0300 Subject: [PATCH 10/47] feat(UtilMention.cs): implemented new utility class - Provides simple method to manage multiple mentions --- src/Modmail.NET/Utils/UtilMention.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/Modmail.NET/Utils/UtilMention.cs diff --git a/src/Modmail.NET/Utils/UtilMention.cs b/src/Modmail.NET/Utils/UtilMention.cs new file mode 100644 index 00000000..2ed3a9fb --- /dev/null +++ b/src/Modmail.NET/Utils/UtilMention.cs @@ -0,0 +1,14 @@ +using System.Text; +using Modmail.NET.Models; + +namespace Modmail.NET.Utils; + +public static class UtilMention +{ + public static string GetMentionsMessageString(IEnumerable permissions) { + var sb = new StringBuilder(); + foreach (var perm in permissions) sb.AppendLine(perm.GetMention()); + + return sb.ToString(); + } +} \ No newline at end of file From f5318eedcf53e24a411b419f36325432c0f687ca Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 16:07:58 +0300 Subject: [PATCH 11/47] feat: Centralize mention handling with UtilMention class - Refactored response methods to use the new `UtilMention` class for generating mentions. This eliminates the need to pass permission info arrays to response methods, simplifying their signatures and reducing dependencies. - Added a new `message edited` response type for future message handling scenarios. --- .../Handlers/ProcessCreateNewTicketHandler.cs | 7 +++-- .../Handlers/ProcessUserSentMessageHandler.cs | 6 +++- src/Modmail.NET/Static/TicketResponses.cs | 30 ++++++++----------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateNewTicketHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateNewTicketHandler.cs index c819756d..a1932b92 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateNewTicketHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateNewTicketHandler.cs @@ -55,8 +55,9 @@ public async Task Handle(ProcessCreateNewTicketCommand request, CancellationToke if (member is null) return; - var newTicketMessageBuilder = TicketResponses.NewTicket(member, ticketId, permissions); - + var pingOnNewTicket = permissions.Where(x => x.PingOnNewTicket).ToArray(); + var msg = TicketResponses.NewTicket(member, ticketId); + msg.WithContent(UtilMention.GetMentionsMessageString(pingOnNewTicket)); var ticketMessage = TicketMessage.MapFrom(ticketId, request.Message, false); @@ -101,7 +102,7 @@ public async Task Handle(ProcessCreateNewTicketCommand request, CancellationToke foreach (var attachment in ticketMessage.Attachments) await _attachmentDownloadService.Handle(attachment.Id, attachment.Url, Path.GetExtension(attachment.FileName)); - await mailChannel.SendMessageAsync(newTicketMessageBuilder); + await mailChannel.SendMessageAsync(msg); var botMessage = await mailChannel.SendMessageAsync(TicketResponses.MessageReceived(request.Message, ticketMessage.Attachments.ToArray())); ticketMessage.BotMessageId = botMessage.Id; diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessUserSentMessageHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessUserSentMessageHandler.cs index aeb16f93..53d13cf1 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessUserSentMessageHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessUserSentMessageHandler.cs @@ -42,7 +42,11 @@ public async Task Handle(ProcessUserSentMessageCommand request, CancellationToke var mailChannel = await _bot.Client.GetChannelAsync(ticket.ModMessageChannelId); var permissions = await _sender.Send(new GetPermissionInfoQuery(), cancellationToken); - var botMessage = await mailChannel.SendMessageAsync(TicketResponses.MessageReceived(request.Message, ticketMessage.Attachments.ToArray(), permissions)); + var pingOnNewTicket = permissions.Where(x => x.PingOnNewMessage).ToArray(); + + var msg = TicketResponses.MessageReceived(request.Message, ticketMessage.Attachments.ToArray()); + msg.WithContent(UtilMention.GetMentionsMessageString(pingOnNewTicket)); + var botMessage = await mailChannel.SendMessageAsync(msg); ticketMessage.BotMessageId = botMessage.Id; _dbContext.Add(ticketMessage); diff --git a/src/Modmail.NET/Static/TicketResponses.cs b/src/Modmail.NET/Static/TicketResponses.cs index bbea43ff..99684484 100644 --- a/src/Modmail.NET/Static/TicketResponses.cs +++ b/src/Modmail.NET/Static/TicketResponses.cs @@ -1,8 +1,6 @@ -using System.Text; -using DSharpPlus.Entities; +using DSharpPlus.Entities; using Modmail.NET.Entities; using Modmail.NET.Extensions; -using Modmail.NET.Models; using Modmail.NET.Utils; namespace Modmail.NET.Static; @@ -12,7 +10,7 @@ namespace Modmail.NET.Static; /// public static class TicketResponses { - public static DiscordMessageBuilder NewTicket(DiscordUser member, Guid ticketId, PermissionInfo[] permissionInfos) { + public static DiscordMessageBuilder NewTicket(DiscordUser member, Guid ticketId) { var embed = new DiscordEmbedBuilder() .WithTitle(LangKeys.NewTicket.GetTranslation()) .WithCustomTimestamp() @@ -29,11 +27,6 @@ public static DiscordMessageBuilder NewTicket(DiscordUser member, Guid ticketId, LangKeys.CloseTicket.GetTranslation(), emoji: new DiscordComponentEmoji("🔒")) ); - - var sb = new StringBuilder(); - foreach (var permissionInfo in permissionInfos.Where(permissionInfo => permissionInfo.PingOnNewTicket)) sb.AppendLine(permissionInfo.GetMention()); - - messageBuilder.WithContent(sb.ToString()); return messageBuilder; } @@ -90,8 +83,7 @@ public static DiscordEmbedBuilder TicketPriorityChanged(DiscordUserInfo modUser, } public static DiscordMessageBuilder MessageReceived(DiscordMessage message, - TicketMessageAttachment[] attachments, - PermissionInfo[] permissions = null) { + TicketMessageAttachment[] attachments) { var embed = new DiscordEmbedBuilder() .WithDescription(message.Content) .WithCustomTimestamp() @@ -101,13 +93,17 @@ public static DiscordMessageBuilder MessageReceived(DiscordMessage message, var msgBuilder = new DiscordMessageBuilder() .AddEmbed(embed) .AddAttachments(attachments); - if (permissions is not null && permissions.Length > 0) { - var sb = new StringBuilder(); - foreach (var permissionInfo in permissions.Where(x => x.PingOnNewMessage)) sb.AppendLine(permissionInfo.GetMention()); + return msgBuilder; + } - msgBuilder.WithContent(sb.ToString()); - } + public static DiscordEmbedBuilder MessageEdited(DiscordMessage message) { + var embed = new DiscordEmbedBuilder() + .WithDescription(message.Content) + .WithCustomTimestamp() + .WithColor(Colors.MessageReceivedColor) + .WithFooter(LangKeys.Edited.GetTranslation()) + .WithUserAsAuthor(message.Author); - return msgBuilder; + return embed; } } \ No newline at end of file From de7903bf2ec6f497a4e4cb94e1079eefc456138c Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 17:04:17 +0300 Subject: [PATCH 12/47] feat(TicketMessageQueue.cs): better log messages --- src/Modmail.NET/Queues/TicketMessageQueue.cs | 143 ++++++++++++++++--- 1 file changed, 120 insertions(+), 23 deletions(-) diff --git a/src/Modmail.NET/Queues/TicketMessageQueue.cs b/src/Modmail.NET/Queues/TicketMessageQueue.cs index b804aa1b..a3f87f0e 100644 --- a/src/Modmail.NET/Queues/TicketMessageQueue.cs +++ b/src/Modmail.NET/Queues/TicketMessageQueue.cs @@ -1,4 +1,5 @@ using DSharpPlus.Entities; +using DSharpPlus.EventArgs; using MediatR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -6,75 +7,171 @@ using Modmail.NET.Aspects; using Modmail.NET.Features.Blacklist; using Modmail.NET.Features.Ticket; -using Modmail.NET.Models.Dto; using Modmail.NET.Utils; using Serilog; namespace Modmail.NET.Queues; -public class TicketMessageQueue : BaseQueue +public class TicketMessageQueue : BaseQueue { private readonly IOptions _options; private readonly IServiceScopeFactory _scopeFactory; - public TicketMessageQueue(IServiceScopeFactory scopeFactory, - IOptions options) : base(TimeSpan.FromMinutes(15)) { + public TicketMessageQueue( + IServiceScopeFactory scopeFactory, + IOptions options + ) : base(TimeSpan.FromMinutes(15)) { _scopeFactory = scopeFactory; _options = options; } - protected override async Task Handle(ulong userId, DiscordTicketMessageDto dto) { - if (dto.Args.Message.Content.StartsWith(_options.Value.BotPrefix)) + protected override async Task Handle(ulong userId, MessageCreatedEventArgs args) { + if (args.Message.Content.StartsWith(_options.Value.BotPrefix)) { + Log.Debug( + "[{Source}] Ignoring message due to bot prefix. UserId: {UserId}, Message: {Message}", + nameof(TicketMessageQueue), + userId, + args.Message.Content + ); return; + } - if (dto.Args.Channel.IsPrivate) - await HandlePrivateTicketMessageAsync(dto.Args.Message, dto.Args.Channel, dto.Args.Author); + if (args.Channel.IsPrivate) + await HandlePrivateTicketMessageAsync(args.Message, args.Channel, args.Author); else - await HandleGuildTicketMessageAsync(dto.Args.Message, dto.Args.Channel, dto.Args.Author, dto.Args.Guild); + await HandleGuildTicketMessageAsync(args.Message, args.Channel, args.Author, args.Guild); } [PerformanceLoggerAspect] - private async Task HandlePrivateTicketMessageAsync(DiscordMessage message, DiscordChannel channel, DiscordUser user) { + private async Task HandlePrivateTicketMessageAsync( + DiscordMessage message, + DiscordChannel channel, + DiscordUser user + ) { + Log.Debug( + "[{Source}] Handling private ticket message. UserId: {UserId}, Message: {Message}", + nameof(TicketMessageQueue), + user.Id, + message.Content + ); + using var scope = _scopeFactory.CreateScope(); + var sender = scope.ServiceProvider.GetRequiredService(); + try { - var sender = scope.ServiceProvider.GetRequiredService(); if (await sender.Send(new CheckUserBlacklistStatusQuery(user.Id))) { + Log.Information( + "[{Source}] User is blacklisted, sending rejection message. UserId: {UserId}", + nameof(TicketMessageQueue), + user.Id + ); await channel.SendMessageAsync(UserResponses.YouHaveBeenBlacklisted()); return; } var activeTicket = await sender.Send(new GetTicketByUserIdQuery(user.Id, true, true)); - if (activeTicket is not null) + if (activeTicket is not null) { + Log.Debug( + "[{Source}] Active ticket found, processing user message. TicketId: {TicketId}, UserId: {UserId}", + nameof(TicketMessageQueue), + activeTicket.Id, + user.Id + ); await sender.Send(new ProcessUserSentMessageCommand(activeTicket.Id, message, channel)); - else + } + else { + Log.Debug( + "[{Source}] No active ticket found, creating a new ticket. UserId: {UserId}", + nameof(TicketMessageQueue), + user.Id + ); await sender.Send(new ProcessCreateNewTicketCommand(user, channel, message)); + } - Log.Information("[TicketMessageQueue] Processed private message from {UserId}: {Message}", user.Id, message.Content); + Log.Information( + "[{Source}] Processed private message. UserId: {UserId}, Message: {Message}", + nameof(TicketMessageQueue), + user.Id, + message.Content + ); } catch (BotExceptionBase ex) { - Log.Warning(ex, "[TicketMessageQueue] Error processing private message from {UserId}: {Message}", user.Id, message.Content); + Log.Warning( + ex, + "[{Source}] BotExceptionBase: Error processing private message. UserId: {UserId}, Message: {Message}", + nameof(TicketMessageQueue), + user.Id, + message.Content + ); } catch (Exception ex) { - Log.Error(ex, "[TicketMessageQueue] Unexpected error processing private message from {UserId}: {Message}", user.Id, message.Content); + Log.Error( + ex, + "[{Source}] Unexpected error processing private message. UserId: {UserId}, Message: {Message}", + nameof(TicketMessageQueue), + user.Id, + message.Content + ); } } [PerformanceLoggerAspect] - private async Task HandleGuildTicketMessageAsync(DiscordMessage message, DiscordChannel channel, DiscordUser modUser, DiscordGuild guild) { + private async Task HandleGuildTicketMessageAsync( + DiscordMessage message, + DiscordChannel channel, + DiscordUser modUser, + DiscordGuild guild + ) { + Log.Debug( + "[{Source}] Handling guild ticket message. ChannelId: {ChannelId}, UserId: {UserId}, Message: {Message}", + nameof(TicketMessageQueue), + channel.Id, + modUser.Id, + message.Content + ); + using var scope = _scopeFactory.CreateScope(); - try { - var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(channel.Topic); - if (ticketId == Guid.Empty) return; + var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(channel.Topic); + if (ticketId == Guid.Empty) { + Log.Warning( + "[{Source}] Invalid ticket id, ignoring message. ChannelId: {ChannelId}, Topic: {Topic}", + nameof(TicketMessageQueue), + channel.Id, + channel.Topic + ); + return; + } + try { var sender = scope.ServiceProvider.GetRequiredService(); await sender.Send(new ProcessModSendMessageCommand(ticketId, modUser, message, channel, guild)); - Log.Information("[TicketMessageQueue] Processed guild message from {UserId}: {Message}", modUser.Id, message.Content); + Log.Information( + "[{Source}] Processed guild message. TicketId: {TicketId}, UserId: {UserId}, Message: {Message}", + nameof(TicketMessageQueue), + ticketId, + modUser.Id, + message.Content + ); } catch (BotExceptionBase ex) { - Log.Warning(ex, "[TicketMessageQueue] Error processing guild message from {UserId}: {Message}", modUser.Id, message.Content); + Log.Warning( + ex, + "[{Source}] BotExceptionBase: Error processing guild message. TicketId: {TicketId}, UserId: {UserId}, Message: {Message}", + nameof(TicketMessageQueue), + ticketId, + modUser.Id, + message.Content + ); } catch (Exception ex) { - Log.Error(ex, "[TicketMessageQueue] Unexpected error processing guild message from {UserId}: {Message}", modUser.Id, message.Content); + Log.Error( + ex, + "[{Source}] Unexpected error processing guild message. TicketId: {TicketId}, UserId: {UserId}, Message: {Message}", + nameof(TicketMessageQueue), + ticketId, + modUser.Id, + message.Content + ); } } } \ No newline at end of file From e7d797406b83b1be37dd216ac775e44dde1c1b4e Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 17:04:45 +0300 Subject: [PATCH 13/47] chore: removed unnecessary DI injection --- src/Modmail.NET.Web.Blazor/Dependency/BusinessDependency.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Modmail.NET.Web.Blazor/Dependency/BusinessDependency.cs b/src/Modmail.NET.Web.Blazor/Dependency/BusinessDependency.cs index afaad400..83194844 100644 --- a/src/Modmail.NET.Web.Blazor/Dependency/BusinessDependency.cs +++ b/src/Modmail.NET.Web.Blazor/Dependency/BusinessDependency.cs @@ -26,7 +26,6 @@ public static void Configure(WebApplicationBuilder builder) { builder.Services.Configure(botConfig); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); From 84f2fb29fd33df909f166dccd86438a74593f148 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 17:09:15 +0300 Subject: [PATCH 14/47] feat: Improve Modmail event handling through modularization - Refactored core Modmail event handling logic into dedicated classes (e.g., ModalSubmittedEvent.cs, ComponentInteractionCreatedEvent.cs) for better organization and maintainability. - Simplified user update event handling. - Decomposed complex event handlers into smaller, more manageable methods, promoting code reusability and testability. - Enhanced event logging with more detailed debug and informational messages for improved diagnostics. - Addressed minor issues related to reaction mirroring. - Changed event dependency ordering to group related events together. --- .../Dependency/DiscordBotDependency.cs | 38 +- .../ComponentInteractionCreatedEvent.cs | 217 +++++++++ src/Modmail.NET/Events/ModalSubmittedEvent.cs | 136 ++++++ .../Events/OnChannelDeletedEvent.cs | 107 +++++ .../Events/OnMessageCreatedEvent.cs | 19 + .../Events/OnMessageDeletedEvent.cs | 187 ++++++++ .../Events/OnMessageReactionAddedEvent.cs | 241 ++++++++++ .../Events/OnMessageReactionRemovedEvent.cs | 241 ++++++++++ .../Events/OnMessageUpdatedEvent.cs | 241 ++++++++++ src/Modmail.NET/Events/UserUpdateEvents.cs | 45 ++ .../Models/Dto/DiscordTicketMessageDto.cs | 6 - src/Modmail.NET/ModmailEventHandlers.cs | 442 ------------------ 12 files changed, 1454 insertions(+), 466 deletions(-) create mode 100644 src/Modmail.NET/Events/ComponentInteractionCreatedEvent.cs create mode 100644 src/Modmail.NET/Events/ModalSubmittedEvent.cs create mode 100644 src/Modmail.NET/Events/OnChannelDeletedEvent.cs create mode 100644 src/Modmail.NET/Events/OnMessageCreatedEvent.cs create mode 100644 src/Modmail.NET/Events/OnMessageDeletedEvent.cs create mode 100644 src/Modmail.NET/Events/OnMessageReactionAddedEvent.cs create mode 100644 src/Modmail.NET/Events/OnMessageReactionRemovedEvent.cs create mode 100644 src/Modmail.NET/Events/OnMessageUpdatedEvent.cs create mode 100644 src/Modmail.NET/Events/UserUpdateEvents.cs delete mode 100644 src/Modmail.NET/Models/Dto/DiscordTicketMessageDto.cs delete mode 100644 src/Modmail.NET/ModmailEventHandlers.cs diff --git a/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs b/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs index b8f10637..fa804cae 100644 --- a/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs +++ b/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs @@ -5,6 +5,7 @@ using DSharpPlus.Extensions; using Modmail.NET.Commands; using Modmail.NET.Commands.Slash; +using Modmail.NET.Events; using Serilog; namespace Modmail.NET.Web.Blazor.Dependency; @@ -44,30 +45,31 @@ public static void Configure(WebApplicationBuilder builder) { builder.Services.ConfigureEventHandlers(eventHandlingBuilder => { - eventHandlingBuilder.HandleMessageCreated(ModmailEventHandlers.OnMessageCreated); - eventHandlingBuilder.HandleChannelDeleted(ModmailEventHandlers.OnChannelDeleted); + eventHandlingBuilder.HandleMessageCreated(OnMessageCreatedEvent.OnMessageCreated); + eventHandlingBuilder.HandleMessageDeleted(OnMessageDeletedEvent.OnMessageDeleted); + eventHandlingBuilder.HandleMessageUpdated(OnMessageUpdatedEvent.OnMessageUpdated); - eventHandlingBuilder.HandleMessageReactionAdded(ModmailEventHandlers.OnMessageReactionAdded); - eventHandlingBuilder.HandleMessageReactionRemoved(ModmailEventHandlers.OnMessageReactionRemoved); + eventHandlingBuilder.HandleMessageReactionAdded(OnMessageReactionAddedEvent.OnMessageReactionAdded); + eventHandlingBuilder.HandleMessageReactionRemoved(OnMessageReactionRemovedEvent.OnMessageReactionRemoved); - //TODO: investigate the need to implement handling of other reaction events - // eventHandlingBuilder.HandleMessageReactionsCleared(); - // eventHandlingBuilder.HandleMessageReactionRemovedEmoji(); + eventHandlingBuilder.HandleChannelDeleted(OnChannelDeletedEvent.OnChannelDeleted); - eventHandlingBuilder.HandleMessageDeleted(ModmailEventHandlers.OnMessageDeleted); - eventHandlingBuilder.HandleMessageUpdated(ModmailEventHandlers.OnMessageUpdated); + eventHandlingBuilder.HandleComponentInteractionCreated(ComponentInteractionCreatedEvent.ComponentInteractionCreated); - eventHandlingBuilder.HandleInteractionCreated(ModmailEventHandlers.InteractionCreated); - eventHandlingBuilder.HandleComponentInteractionCreated(ModmailEventHandlers.ComponentInteractionCreated); - eventHandlingBuilder.HandleModalSubmitted(ModmailEventHandlers.ModalSubmitted); + eventHandlingBuilder.HandleModalSubmitted(ModalSubmittedEvent.ModalSubmitted); - eventHandlingBuilder.HandleGuildMemberAdded(ModmailEventHandlers.OnGuildMemberAdded); - eventHandlingBuilder.HandleGuildMemberRemoved(ModmailEventHandlers.OnGuildMemberRemoved); - eventHandlingBuilder.HandleGuildBanAdded(ModmailEventHandlers.OnGuildBanAdded); - eventHandlingBuilder.HandleGuildBanRemoved(ModmailEventHandlers.OnGuildBanRemoved); + //TODO: investigate the need to implement handling of other reaction events + // eventHandlingBuilder.HandleMessageReactionsCleared(); + // eventHandlingBuilder.HandleMessageReactionRemovedEmoji(); - eventHandlingBuilder.HandleUserUpdated(ModmailEventHandlers.OnUserUpdated); - eventHandlingBuilder.HandleUserSettingsUpdated(ModmailEventHandlers.OnUserSettingsUpdated); + //User update + eventHandlingBuilder.HandleInteractionCreated(UserUpdateEvents.InteractionCreated); + eventHandlingBuilder.HandleGuildMemberAdded(UserUpdateEvents.OnGuildMemberAdded); + eventHandlingBuilder.HandleGuildMemberRemoved(UserUpdateEvents.OnGuildMemberRemoved); + eventHandlingBuilder.HandleGuildBanAdded(UserUpdateEvents.OnGuildBanAdded); + eventHandlingBuilder.HandleGuildBanRemoved(UserUpdateEvents.OnGuildBanRemoved); + eventHandlingBuilder.HandleUserUpdated(UserUpdateEvents.OnUserUpdated); + eventHandlingBuilder.HandleUserSettingsUpdated(UserUpdateEvents.OnUserSettingsUpdated); }); } } \ No newline at end of file diff --git a/src/Modmail.NET/Events/ComponentInteractionCreatedEvent.cs b/src/Modmail.NET/Events/ComponentInteractionCreatedEvent.cs new file mode 100644 index 00000000..2c4c34de --- /dev/null +++ b/src/Modmail.NET/Events/ComponentInteractionCreatedEvent.cs @@ -0,0 +1,217 @@ +using DSharpPlus; +using DSharpPlus.Entities; +using DSharpPlus.EventArgs; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using Modmail.NET.Abstract; +using Modmail.NET.Aspects; +using Modmail.NET.Features.Ticket; +using Modmail.NET.Features.UserInfo; +using Modmail.NET.Utils; +using Serilog; + +namespace Modmail.NET.Events; + +public static class ComponentInteractionCreatedEvent +{ + [PerformanceLoggerAspect] + public static async Task ComponentInteractionCreated( + DiscordClient client, + ComponentInteractionCreatedEventArgs args + ) { + Log.Debug( + "[{Source}] Component interaction created. CustomId: {CustomId}, UserId: {UserId}, ChannelId: {ChannelId}, InteractionId: {InteractionId}, MessageId: {MessageId}", + nameof(ComponentInteractionCreatedEvent), + args.Interaction?.Data?.CustomId, + args.User?.Id, + args.Channel?.Id, + args.Interaction?.Id, + args.Message?.Id + ); + + using var scope = client.ServiceProvider.CreateScope(); + var sender = scope.ServiceProvider.GetRequiredService(); + + try { + await sender.Send(new UpdateDiscordUserCommand(args.User)); + + var key = args.Interaction?.Data?.CustomId; + var (interactionName, parameters) = UtilInteraction.ParseKey(key); + var messageId = args.Message.Id; + + switch (interactionName) { + case "star": + await ProcessStarInteraction(sender, args, messageId); + break; + case "ticket_type": + await ProcessTicketTypeInteraction(sender, args, messageId); + break; + case "close_ticket": // This must stay due to deprecation and support for existing tickets (v2.0 beta) + case "close_ticket_with_reason": + await ProcessCloseTicketInteraction(sender, args, messageId); + break; + default: + Log.Warning( + "[{Source}] Unknown interaction name: {InteractionName}, CustomId: {CustomId}", + nameof(ComponentInteractionCreatedEvent), + interactionName, + args.Interaction?.Data?.CustomId + ); + break; + } + } + catch (BotExceptionBase ex) { + Log.Warning( + ex, + "[{Source}] BotExceptionBase: Error processing component interaction. CustomId: {CustomId}, UserId: {UserId}, ChannelId: {ChannelId}, InteractionId: {InteractionId}, MessageId: {MessageId}", + nameof(ComponentInteractionCreatedEvent), + args.Interaction?.Data?.CustomId, + args.User?.Id, + args.Channel?.Id, + args.Interaction?.Id, + args.Message?.Id + ); + } + catch (Exception ex) { + Log.Error( + ex, + "[{Source}] Unhandled exception processing component interaction. CustomId: {CustomId}, UserId: {UserId}, ChannelId: {ChannelId}, InteractionId: {InteractionId}, MessageId: {MessageId}", + nameof(ComponentInteractionCreatedEvent), + args.Interaction?.Data?.CustomId, + args.User?.Id, + args.Channel?.Id, + args.Interaction?.Id, + args.Message?.Id + ); + } + } + + private static async Task ProcessStarInteraction( + ISender sender, + ComponentInteractionCreatedEventArgs args, + ulong messageId + ) { + var key = args.Interaction?.Data?.CustomId; + try { + var (interactionName, parameters) = UtilInteraction.ParseKey(key); + //feedback process show modal + var starParam = parameters[0]; + var ticketIdParam = parameters[1]; + + var starCount = int.Parse(starParam); + var ticketId = Guid.Parse(ticketIdParam); + + var feedbackModal = Modals.CreateFeedbackModal(starCount, ticketId, messageId); + + await args.Interaction.CreateResponseAsync( + DiscordInteractionResponseType.Modal, + feedbackModal + ); + + Log.Information( + "[{Source}] Star interaction processed. TicketId: {TicketId}, StarCount: {StarCount}, InteractionId: {InteractionId}", + nameof(ComponentInteractionCreatedEvent), + ticketId, + starCount, + args.Interaction?.Id + ); + } + catch (Exception ex) { + Log.Error( + ex, + "[{Source}] Error processing star interaction submission. CustomId: {CustomId}, InteractionId: {InteractionId}", + nameof(ComponentInteractionCreatedEvent), + args.Interaction?.Data?.CustomId, + args.Interaction?.Id + ); + } + } + + private static async Task ProcessTicketTypeInteraction( + ISender sender, + ComponentInteractionCreatedEventArgs args, + ulong messageId + ) { + var key = args.Interaction?.Data?.CustomId; + try { + await args.Interaction.CreateResponseAsync( + DiscordInteractionResponseType.UpdateMessage + ); + var (interactionName, parameters) = UtilInteraction.ParseKey(key); + + var ticketIdParam = parameters[0]; + var ticketId = Guid.Parse(ticketIdParam); + var selectedTypeKey = args.Values.FirstOrDefault(); + if (string.IsNullOrEmpty(selectedTypeKey)) { + Log.Warning( + "[{Source}] No ticket type selected. InteractionId: {InteractionId}, MessageId: {MessageId}", + nameof(ComponentInteractionCreatedEvent), + args.Interaction?.Id, + messageId + ); + return; + } + + await sender.Send(new ProcessChangeTicketTypeCommand( + ticketId, + selectedTypeKey, + null, + args.Channel, + args.Message, + args.User.Id + )); + + Log.Information( + "[{Source}] Ticket type changed. TicketId: {TicketId}, SelectedTypeKey: {SelectedTypeKey}, InteractionId: {InteractionId}", + nameof(ComponentInteractionCreatedEvent), + ticketId, + selectedTypeKey, + args.Interaction?.Id + ); + } + catch (Exception ex) { + Log.Error( + ex, + "[{Source}] Error processing process ticket type interaction. CustomId: {CustomId}, InteractionId: {InteractionId}", + nameof(ComponentInteractionCreatedEvent), + args.Interaction?.Data?.CustomId, + args.Interaction?.Id + ); + } + } + + private static async Task ProcessCloseTicketInteraction( + ISender sender, + ComponentInteractionCreatedEventArgs args, + ulong messageId + ) { + var key = args.Interaction?.Data?.CustomId; + try { + var (interactionName, parameters) = UtilInteraction.ParseKey(key); + + var ticketIdParam = parameters[0]; + var ticketId = Guid.Parse(ticketIdParam); + var modal = Modals.CreateCloseTicketWithReasonModal(ticketId); + await args.Interaction.CreateResponseAsync( + DiscordInteractionResponseType.Modal, + modal + ); + + Log.Information( + "[{Source}] Close ticket interaction triggered. TicketId: {TicketId}, InteractionId: {InteractionId}", + nameof(ComponentInteractionCreatedEvent), + ticketId, + args.Interaction?.Id + ); + } + catch (Exception ex) { + Log.Error( + ex, + "[{Source}] Error processing process close ticket interaction. CustomId: {CustomId}, InteractionId: {InteractionId}", + nameof(ComponentInteractionCreatedEvent), + args.Interaction?.Data?.CustomId, + args.Interaction?.Id + ); + } + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Events/ModalSubmittedEvent.cs b/src/Modmail.NET/Events/ModalSubmittedEvent.cs new file mode 100644 index 00000000..5d90c33c --- /dev/null +++ b/src/Modmail.NET/Events/ModalSubmittedEvent.cs @@ -0,0 +1,136 @@ +using DSharpPlus; +using DSharpPlus.Entities; +using DSharpPlus.EventArgs; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using Modmail.NET.Abstract; +using Modmail.NET.Aspects; +using Modmail.NET.Features.Ticket; +using Modmail.NET.Features.UserInfo; +using Modmail.NET.Utils; +using Serilog; + +namespace Modmail.NET.Events; + +public static class ModalSubmittedEvent +{ + [PerformanceLoggerAspect] + public static async Task ModalSubmitted( + DiscordClient client, + ModalSubmittedEventArgs args + ) { + Log.Debug( + "[{Source}] Modal submitted. CustomId: {CustomId}, InteractionId: {InteractionId}", + nameof(ModalSubmittedEvent), + args.Interaction.Data.CustomId, + args.Interaction.Id + ); + + await args.Interaction.CreateResponseAsync( + DiscordInteractionResponseType.ChannelMessageWithSource, + new DiscordInteractionResponseBuilder().AsEphemeral() + .WithContent(LangProvider.This.GetTranslation(LangKeys.ThankYouForFeedback)) + ); + + using var scope = client.ServiceProvider.CreateScope(); + var sender = scope.ServiceProvider.GetRequiredService(); + + try { + await sender.Send(new UpdateDiscordUserCommand(args.Interaction.User)); + + var (interactionName, parameters) = + UtilInteraction.ParseKey(args.Interaction.Data.CustomId); + + switch (interactionName) { + case "feedback": + await ProcessFeedback(sender, args, parameters); + break; + case "close_ticket_with_reason": + await ProcessCloseTicketWithReason(sender, args, parameters); + break; + default: + Log.Warning( + "[{Source}] Unknown interaction name: {InteractionName}, CustomId: {CustomId}", + nameof(ModalSubmittedEvent), + interactionName, + args.Interaction.Data.CustomId + ); + break; + } + } + catch (BotExceptionBase ex) { + Log.Warning( + ex, + "[{Source}] BotExceptionBase: Error processing modal submission. CustomId: {CustomId}, InteractionId: {InteractionId}", + nameof(ModalSubmittedEvent), + args.Interaction.Data.CustomId, + args.Interaction.Id + ); + } + catch (Exception ex) { + Log.Error( + ex, + "[{Source}] Unhandled exception processing modal submission. CustomId: {CustomId}, InteractionId: {InteractionId}", + nameof(ModalSubmittedEvent), + args.Interaction.Data.CustomId, + args.Interaction.Id + ); + } + } + + private static async Task ProcessFeedback( + ISender sender, + ModalSubmittedEventArgs args, + string[] parameters + ) { + var textInput = args.Values["feedback"]; + + var starParam = parameters[0]; + var ticketIdParam = parameters[1]; + var messageIdParam = parameters[2]; + + var starCount = int.Parse(starParam); + var ticketId = Guid.Parse(ticketIdParam); + var feedbackMessageId = ulong.Parse(messageIdParam); + + var feedbackMessage = await args.Interaction.Channel.GetMessageAsync(feedbackMessageId); + await sender.Send(new ProcessAddFeedbackCommand( + ticketId, + starCount, + textInput, + feedbackMessage + )); + + Log.Information( + "[{Source}] Feedback processed. TicketId: {TicketId}, StarCount: {StarCount}, InteractionId: {InteractionId}", + nameof(ModalSubmittedEvent), + ticketId, + starCount, + args.Interaction.Id + ); + } + + private static async Task ProcessCloseTicketWithReason( + ISender sender, + ModalSubmittedEventArgs args, + string[] parameters + ) { + var textInput = args.Values["reason"]; + var ticketIdParam = parameters[0]; + var ticketId = Guid.Parse(ticketIdParam); + + await sender.Send(new ProcessCloseTicketCommand( + ticketId, + args.Interaction.User.Id, + textInput, + args.Interaction.Channel + )); + + Log.Information( + "[{Source}] Ticket closed with reason. TicketId: {TicketId}, InteractionId: {InteractionId}", + nameof(ModalSubmittedEvent), + ticketId, + args.Interaction.Id + ); + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Events/OnChannelDeletedEvent.cs b/src/Modmail.NET/Events/OnChannelDeletedEvent.cs new file mode 100644 index 00000000..82feb542 --- /dev/null +++ b/src/Modmail.NET/Events/OnChannelDeletedEvent.cs @@ -0,0 +1,107 @@ +using DSharpPlus; +using DSharpPlus.Entities.AuditLogs; +using DSharpPlus.EventArgs; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using Modmail.NET.Abstract; +using Modmail.NET.Aspects; +using Modmail.NET.Features.Ticket; +using Modmail.NET.Features.UserInfo; +using Modmail.NET.Utils; +using Serilog; + +namespace Modmail.NET.Events; + +public static class OnChannelDeletedEvent +{ + [PerformanceLoggerAspect] + public static async Task OnChannelDeleted( + DiscordClient client, + ChannelDeletedEventArgs args + ) { + Log.Debug( + "[{Source}] Channel deletion event triggered. ChannelId: {ChannelId}", + nameof(OnChannelDeletedEvent), + args.Channel.Id + ); + + using var scope = client.ServiceProvider.CreateScope(); + var sender = scope.ServiceProvider.GetRequiredService(); + var langData = scope.ServiceProvider.GetRequiredService(); + + var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(args.Channel.Topic); + if (ticketId == Guid.Empty) { + Log.Debug( + "[{Source}] Channel is not a ticket channel, ignoring. ChannelId: {ChannelId}", + nameof(OnChannelDeletedEvent), + args.Channel.Id + ); + return; + } + + try { + var auditLogEntry = await args.Guild + .GetAuditLogsAsync(1, null, DiscordAuditLogActionType.ChannelDelete) + .FirstOrDefaultAsync(); + var user = auditLogEntry?.UserResponsible ?? client.CurrentUser; + Log.Debug( + "[{Source}] Channel deletion initiated by user: {UserId}, ChannelId: {ChannelId}", + nameof(OnChannelDeletedEvent), + user.Id, + args.Channel.Id + ); + await sender.Send(new UpdateDiscordUserCommand(user)); + + var ticket = await sender.Send(new GetTicketQuery(ticketId, true)); + if (ticket is null) { + Log.Warning( + "[{Source}] Could not retrieve ticket information, possibly already deleted. TicketId: {TicketId}, ChannelId: {ChannelId}", + nameof(OnChannelDeletedEvent), + ticketId, + args.Channel.Id + ); + return; + } + + if (ticket.ClosedDateUtc.HasValue) { + Log.Information( + "[{Source}] Ticket already closed, ignoring channel deletion event. TicketId: {TicketId}, ChannelId: {ChannelId}", + nameof(OnChannelDeletedEvent), + ticketId, + args.Channel.Id + ); + return; // Ticket is already closed + } + + await sender.Send(new ProcessCloseTicketCommand( + ticketId, + user.Id, + langData.GetTranslation(LangKeys.ChannelWasDeleted), + args.Channel + )); + + Log.Information( + "[{Source}] Ticket channel deleted. TicketId: {TicketId}, ChannelId: {ChannelId}", + nameof(OnChannelDeletedEvent), + ticketId, + args.Channel.Id + ); + } + catch (BotExceptionBase ex) { + Log.Warning( + ex, + "[{Source}] BotExceptionBase: Error processing channel deletion. ChannelId: {ChannelId}", + nameof(OnChannelDeletedEvent), + args.Channel.Id + ); + } + catch (Exception ex) { + Log.Error( + ex, + "[{Source}] Unhandled exception processing channel deletion. ChannelId: {ChannelId}", + nameof(OnChannelDeletedEvent), + args.Channel.Id + ); + } + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Events/OnMessageCreatedEvent.cs b/src/Modmail.NET/Events/OnMessageCreatedEvent.cs new file mode 100644 index 00000000..c77f0999 --- /dev/null +++ b/src/Modmail.NET/Events/OnMessageCreatedEvent.cs @@ -0,0 +1,19 @@ +using DSharpPlus; +using DSharpPlus.EventArgs; +using Microsoft.Extensions.DependencyInjection; +using Modmail.NET.Queues; + +namespace Modmail.NET.Events; + +public static class OnMessageCreatedEvent +{ + public static async Task OnMessageCreated(DiscordClient client, MessageCreatedEventArgs args) { + _ = UserUpdateEvents.UpdateUser(client, args.Author); + if (args.Message.Author?.IsBot == true) return; + if (args.Message.IsTTS) return; + + var scope = client.ServiceProvider.CreateScope(); + var ticketMessageQueue = scope.ServiceProvider.GetRequiredService(); + await ticketMessageQueue.Enqueue(args.Author.Id, args); + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Events/OnMessageDeletedEvent.cs b/src/Modmail.NET/Events/OnMessageDeletedEvent.cs new file mode 100644 index 00000000..33bf8977 --- /dev/null +++ b/src/Modmail.NET/Events/OnMessageDeletedEvent.cs @@ -0,0 +1,187 @@ +using DSharpPlus; +using DSharpPlus.EventArgs; +using DSharpPlus.Exceptions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Modmail.NET.Aspects; +using Modmail.NET.Database; +using Modmail.NET.Utils; +using Serilog; + +namespace Modmail.NET.Events; + +public static class OnMessageDeletedEvent +{ + [PerformanceLoggerAspect] + public static async Task OnMessageDeleted( + DiscordClient client, + MessageDeletedEventArgs args + ) { + _ = UserUpdateEvents.UpdateUser(client, args.Message.Author); + + if (args.Message.Author?.IsBot == true) { + Log.Debug( + "[{Source}] Ignoring message deletion from bot. MessageId: {MessageId}, ChannelId: {ChannelId}", + nameof(OnMessageDeletedEvent), + args.Message.Id, + args.Channel.Id + ); + return; + } + + using var scope = client.ServiceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + if (args.Channel.IsPrivate) + await ProcessPrivateMessageDeletion(client, dbContext, args); + else + await ProcessTicketChannelMessageDeletion(client, dbContext, args); + } + + private static async Task ProcessPrivateMessageDeletion( + DiscordClient client, + ModmailDbContext dbContext, + MessageDeletedEventArgs args + ) { + Log.Debug( + "[{Source}] Processing private message deletion. MessageId: {MessageId}, ChannelId: {ChannelId}", + nameof(OnMessageDeletedEvent), + args.Message.Id, + args.Channel.Id + ); + + var messageEntity = await dbContext.TicketMessages + .FirstOrDefaultAsync( + x => !x.SentByMod && x.MessageDiscordId == args.Message.Id + ); + if (messageEntity is null) { + Log.Debug( + "[{Source}] No TicketMessage entity found for deleted private message. MessageId: {MessageId}", + nameof(OnMessageDeletedEvent), + args.Message.Id + ); + return; + } + + var ticket = await dbContext.Tickets.FirstOrDefaultAsync(x => + !x.ClosedDateUtc.HasValue && x.Id == messageEntity.TicketId && + x.PrivateMessageChannelId == args.Channel.Id); + if (ticket is null) { + Log.Warning( + "[{Source}] No active ticket found for deleted private message. MessageId: {MessageId}, TicketId: {TicketId}", + nameof(OnMessageDeletedEvent), + args.Message.Id, + messageEntity.TicketId + ); + return; + } + + await DeleteMirroredMessage( + client, + ticket.ModMessageChannelId, + messageEntity.BotMessageId + ); + } + + private static async Task ProcessTicketChannelMessageDeletion( + DiscordClient client, + ModmailDbContext dbContext, + MessageDeletedEventArgs args + ) { + Log.Debug( + "[{Source}] Processing ticket channel message deletion. MessageId: {MessageId}, ChannelId: {ChannelId}", + nameof(OnMessageDeletedEvent), + args.Message.Id, + args.Channel.Id + ); + + var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(args.Channel.Topic); + if (ticketId == Guid.Empty) { + Log.Warning( + "[{Source}] Could not extract valid TicketId from channel topic. ChannelId: {ChannelId}, Topic: {Topic}", + nameof(OnMessageDeletedEvent), + args.Channel.Id, + args.Channel.Topic + ); + return; + } + + var messageEntity = await dbContext.TicketMessages.FirstOrDefaultAsync(x => + x.SentByMod && x.TicketId == ticketId && x.MessageDiscordId == args.Message.Id); + if (messageEntity is null) { + Log.Debug( + "[{Source}] No TicketMessage entity found for deleted ticket channel message. MessageId: {MessageId}, TicketId: {TicketId}", + nameof(OnMessageDeletedEvent), + args.Message.Id, + ticketId + ); + return; + } + + var ticket = await dbContext.Tickets.FirstOrDefaultAsync(x => + !x.ClosedDateUtc.HasValue && x.Id == ticketId && + x.ModMessageChannelId == args.Channel.Id); + if (ticket is null) { + Log.Warning( + "[{Source}] No active ticket found for deleted ticket channel message. MessageId: {MessageId}, TicketId: {TicketId}", + nameof(OnMessageDeletedEvent), + args.Message.Id, + ticketId + ); + return; + } + + await DeleteMirroredMessage( + client, + ticket.PrivateMessageChannelId, + messageEntity.BotMessageId + ); + } + + private static async Task DeleteMirroredMessage( + DiscordClient client, + ulong? channelId, + ulong? botMessageId + ) { + if (!channelId.HasValue || !botMessageId.HasValue) { + Log.Warning( + "[{Source}] Cannot delete mirrored message. Invalid ChannelId or BotMessageId. ChannelId: {ChannelId}, BotMessageId: {BotMessageId}", + nameof(OnMessageDeletedEvent), + channelId, + botMessageId + ); + return; + } + + try { + var channel = await client.GetChannelAsync(channelId.Value); + var message = await channel.GetMessageAsync(botMessageId.Value); + await message.DeleteAsync(); + Log.Information( + "[{Source}] Processed message deleted {ChannelId} {MessageId} " + + "{MessageAuthor}", + nameof(OnMessageDeletedEvent), + channel.Id, + message.Id, + message.Author?.Id + ); + } + catch (NotFoundException) { + Log.Warning( + "[{Source}] Mirrored message not found, assuming it was already deleted. ChannelId: {ChannelId}, BotMessageId: {BotMessageId}", + nameof(OnMessageDeletedEvent), + channelId, + botMessageId + ); + } + catch (Exception ex) { + Log.Error( + ex, + "[{Source}] An error occurred while deleting the mirrored message. ChannelId: {ChannelId}, BotMessageId: {BotMessageId}", + nameof(OnMessageDeletedEvent), + channelId, + botMessageId + ); + } + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Events/OnMessageReactionAddedEvent.cs b/src/Modmail.NET/Events/OnMessageReactionAddedEvent.cs new file mode 100644 index 00000000..d452fbf1 --- /dev/null +++ b/src/Modmail.NET/Events/OnMessageReactionAddedEvent.cs @@ -0,0 +1,241 @@ +using DSharpPlus; +using DSharpPlus.Entities; +using DSharpPlus.EventArgs; +using DSharpPlus.Exceptions; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Modmail.NET.Aspects; +using Modmail.NET.Database; +using Modmail.NET.Features.UserInfo; +using Modmail.NET.Utils; +using Serilog; + +namespace Modmail.NET.Events; + +public class OnMessageReactionAddedEvent +{ + [PerformanceLoggerAspect] + public static async Task OnMessageReactionAdded( + DiscordClient client, + MessageReactionAddedEventArgs args + ) { + if (args is null) { + Log.Debug( + "[{Source}] MessageReactionAddedEventArgs is null, exiting", + nameof(OnMessageReactionAddedEvent) + ); + return; + } + + if (args.User.IsBot) { + Log.Debug( + "[{Source}] Ignoring reaction added by bot. UserId: {UserId}", + nameof(OnMessageReactionAddedEvent), + args.User.Id + ); + return; + } + + if (args.Emoji.Name == Const.ProcessedReactionDiscordEmojiUnicode) { + Log.Debug( + "[{Source}] Ignoring processed reaction emoji. EmojiName: {EmojiName}", + nameof(OnMessageReactionAddedEvent), + args.Emoji.Name + ); + return; + } + + using var scope = client.ServiceProvider.CreateScope(); + var sender = scope.ServiceProvider.GetRequiredService(); + await sender.Send(new UpdateDiscordUserCommand(args.User)); + + if (args.Channel.IsPrivate) + await ProcessPrivateChannelReaction(client, scope, args); + else + await ProcessTicketChannelReaction(client, scope, args); + } + + private static async Task ProcessPrivateChannelReaction( + DiscordClient client, + IServiceScope scope, + MessageReactionAddedEventArgs args + ) { + Log.Debug( + "[{Source}] Processing reaction added in private channel. ChannelId: {ChannelId}, MessageId: {MessageId}, Emoji: {Emoji}", + nameof(OnMessageReactionAddedEvent), + args.Channel.Id, + args.Message.Id, + args.Emoji.Name + ); + + if (args.Message.Author?.Id != client.CurrentUser.Id) { + Log.Debug( + "[{Source}] Ignoring reaction removal not from bot's own message in private channel. ChannelId: {ChannelId}, MessageId: {MessageId}", + nameof(OnMessageReactionRemovedEvent), + args.Channel.Id, + args.Message.Id + ); + return; + } + + + if (args.User.Id == client.CurrentUser.Id) { + Log.Debug( + "[{Source}] Ignoring bots reaction in private channel. ChannelId: {ChannelId}, MessageId: {MessageId}", + nameof(OnMessageReactionAddedEvent), + args.Channel.Id, + args.Message.Id + ); + return; + } + + var dbContext = scope.ServiceProvider.GetRequiredService(); + var messageEntity = await dbContext.TicketMessages + .FirstOrDefaultAsync(x => x.SentByMod && x.BotMessageId == args.Message.Id); + if (messageEntity is null) { + Log.Debug( + "[{Source}] No matching TicketMessage found for reaction in private channel. ChannelId: {ChannelId}, MessageId: {MessageId}", + nameof(OnMessageReactionAddedEvent), + args.Channel.Id, + args.Message.Id + ); + return; + } + + var ticket = await dbContext.Tickets.FirstOrDefaultAsync(x => + !x.ClosedDateUtc.HasValue && x.OpenerUserId == args.User.Id && + x.Id == messageEntity.TicketId && x.PrivateMessageChannelId == args.Channel.Id); + if (ticket is null) { + Log.Warning( + "[{Source}] No active ticket found for reaction in private channel. ChannelId: {ChannelId}, MessageId: {MessageId}, TicketId: {TicketId}", + nameof(OnMessageReactionAddedEvent), + args.Channel.Id, + args.Message.Id, + messageEntity.TicketId + ); + return; + } + + await AddReactionToMirroredMessage( + client, + ticket.ModMessageChannelId, + messageEntity.MessageDiscordId, + args.Emoji + ); + } + + private static async Task ProcessTicketChannelReaction( + DiscordClient client, + IServiceScope scope, + MessageReactionAddedEventArgs args + ) { + Log.Debug( + "[{Source}] Processing reaction added in ticket channel. ChannelId: {ChannelId}, MessageId: {MessageId}, Emoji: {Emoji}", + nameof(OnMessageReactionAddedEvent), + args.Channel.Id, + args.Message.Id, + args.Emoji.Name + ); + + var topic = args.Channel.Topic; + var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(topic); + if (ticketId == Guid.Empty) { + Log.Warning( + "[{Source}] Could not extract valid TicketId from channel topic. ChannelId: {ChannelId}, Topic: {Topic}", + nameof(OnMessageReactionAddedEvent), + args.Channel.Id, + topic + ); + return; + } + + var dbContext = scope.ServiceProvider.GetRequiredService(); + + var messageEntity = await dbContext.TicketMessages.FirstOrDefaultAsync(x => + !x.SentByMod && x.BotMessageId == args.Message.Id && x.TicketId == ticketId); + if (messageEntity is null) { + Log.Debug( + "[{Source}] No matching TicketMessage found for reaction in ticket channel. ChannelId: {ChannelId}, MessageId: {MessageId}, TicketId: {TicketId}", + nameof(OnMessageReactionAddedEvent), + args.Channel.Id, + args.Message.Id, + ticketId + ); + return; + } + + var ticket = await dbContext.Tickets.FirstOrDefaultAsync(x => + !x.ClosedDateUtc.HasValue && x.Id == ticketId && + x.ModMessageChannelId == args.Channel.Id); + if (ticket is null) { + Log.Warning( + "[{Source}] No active ticket found for reaction in ticket channel. ChannelId: {ChannelId}, MessageId: {MessageId}, TicketId: {TicketId}", + nameof(OnMessageReactionAddedEvent), + args.Channel.Id, + args.Message.Id, + ticketId + ); + return; + } + + await AddReactionToMirroredMessage( + client, + ticket.PrivateMessageChannelId, + messageEntity.MessageDiscordId, + args.Emoji + ); + } + + private static async Task AddReactionToMirroredMessage( + DiscordClient client, + ulong? channelId, + ulong? messageId, + DiscordEmoji emoji + ) { + if (!channelId.HasValue || !messageId.HasValue) { + Log.Warning( + "[{Source}] Cannot add reaction to mirrored message. Invalid ChannelId or MessageId. ChannelId: {ChannelId}, MessageId: {MessageId}, Emoji: {Emoji}", + nameof(OnMessageReactionAddedEvent), + channelId, + messageId, + emoji.Name + ); + return; + } + + try { + var channel = await client.GetChannelAsync(channelId.Value); + var message = await channel.GetMessageAsync(messageId.Value); + await message.CreateReactionAsync(emoji); + Log.Information( + "[{Source}] Processed reaction added {ChannelId} {MessageId} " + + "{MessageAuthor} {Emoji}", + nameof(OnMessageReactionAddedEvent), + channel.Id, + message.Id, + message.Author?.Id, + emoji.Name + ); + } + catch (NotFoundException) { + Log.Warning( + "[{Source}] Mirrored message not found, cannot add reaction. ChannelId: {ChannelId}, MessageId: {MessageId}, Emoji: {Emoji}", + nameof(OnMessageReactionAddedEvent), + channelId, + messageId, + emoji.Name + ); + } + catch (Exception ex) { + Log.Error( + ex, + "[{Source}] An error occurred while adding the reaction to the mirrored message. ChannelId: {ChannelId}, MessageId: {MessageId}, Emoji: {Emoji}", + nameof(OnMessageReactionAddedEvent), + channelId, + messageId, + emoji.Name + ); + } + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Events/OnMessageReactionRemovedEvent.cs b/src/Modmail.NET/Events/OnMessageReactionRemovedEvent.cs new file mode 100644 index 00000000..3f938de2 --- /dev/null +++ b/src/Modmail.NET/Events/OnMessageReactionRemovedEvent.cs @@ -0,0 +1,241 @@ +using DSharpPlus; +using DSharpPlus.Entities; +using DSharpPlus.EventArgs; +using DSharpPlus.Exceptions; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Modmail.NET.Aspects; +using Modmail.NET.Database; +using Modmail.NET.Features.UserInfo; +using Modmail.NET.Utils; +using Serilog; + +namespace Modmail.NET.Events; + +public static class OnMessageReactionRemovedEvent +{ + [PerformanceLoggerAspect] + public static async Task OnMessageReactionRemoved( + DiscordClient client, + MessageReactionRemovedEventArgs args + ) { + if (args is null) { + Log.Debug( + "[{Source}] MessageReactionRemovedEventArgs is null, exiting", + nameof(OnMessageReactionRemovedEvent) + ); + return; + } + + if (args.User.IsBot) { + Log.Debug( + "[{Source}] Ignoring reaction removed by bot. UserId: {UserId}", + nameof(OnMessageReactionRemovedEvent), + args.User.Id + ); + return; + } + + if (args.Emoji.Name == Const.ProcessedReactionDiscordEmojiUnicode) { + Log.Debug( + "[{Source}] Ignoring processed reaction emoji removed. EmojiName: {EmojiName}", + nameof(OnMessageReactionRemovedEvent), + args.Emoji.Name + ); + return; + } + + using var scope = client.ServiceProvider.CreateScope(); + var sender = scope.ServiceProvider.GetRequiredService(); + await sender.Send(new UpdateDiscordUserCommand(args.User)); + + if (args.Channel.IsPrivate) + await ProcessPrivateChannelReactionRemoval(client, scope, args); + else + await ProcessTicketChannelReactionRemoval(client, scope, args); + } + + private static async Task ProcessPrivateChannelReactionRemoval( + DiscordClient client, + IServiceScope scope, + MessageReactionRemovedEventArgs args + ) { + Log.Debug( + "[{Source}] Processing reaction removed in private channel. ChannelId: {ChannelId}, MessageId: {MessageId}, Emoji: {Emoji}", + nameof(OnMessageReactionRemovedEvent), + args.Channel.Id, + args.Message.Id, + args.Emoji.Name + ); + + if (args.Message.Author?.Id != client.CurrentUser.Id) { + Log.Debug( + "[{Source}] Ignoring reaction removal not from bot's own message in private channel. ChannelId: {ChannelId}, MessageId: {MessageId}", + nameof(OnMessageReactionRemovedEvent), + args.Channel.Id, + args.Message.Id + ); + return; + } + + if (args.User.Id == client.CurrentUser.Id) { + Log.Debug( + "[{Source}] Ignoring bots reaction in private channel. ChannelId: {ChannelId}, MessageId: {MessageId}", + nameof(OnMessageReactionAddedEvent), + args.Channel.Id, + args.Message.Id + ); + return; + } + + + var dbContext = scope.ServiceProvider.GetRequiredService(); + var messageEntity = await dbContext.TicketMessages + .FirstOrDefaultAsync(x => x.SentByMod && x.BotMessageId == args.Message.Id); + if (messageEntity is null) { + Log.Debug( + "[{Source}] No matching TicketMessage found for reaction removal in private channel. ChannelId: {ChannelId}, MessageId: {MessageId}", + nameof(OnMessageReactionRemovedEvent), + args.Channel.Id, + args.Message.Id + ); + return; + } + + var ticket = await dbContext.Tickets.FirstOrDefaultAsync(x => + !x.ClosedDateUtc.HasValue && x.OpenerUserId == args.User.Id && + x.Id == messageEntity.TicketId && x.PrivateMessageChannelId == args.Channel.Id); + if (ticket is null) { + Log.Warning( + "[{Source}] No active ticket found for reaction removal in private channel. ChannelId: {ChannelId}, MessageId: {MessageId}, TicketId: {TicketId}", + nameof(OnMessageReactionRemovedEvent), + args.Channel.Id, + args.Message.Id, + messageEntity.TicketId + ); + return; + } + + await RemoveReactionFromMirroredMessage( + client, + ticket.ModMessageChannelId, + messageEntity.MessageDiscordId, + args.Emoji + ); + } + + private static async Task ProcessTicketChannelReactionRemoval( + DiscordClient client, + IServiceScope scope, + MessageReactionRemovedEventArgs args + ) { + Log.Debug( + "[{Source}] Processing reaction removed in ticket channel. ChannelId: {ChannelId}, MessageId: {MessageId}, Emoji: {Emoji}", + nameof(OnMessageReactionRemovedEvent), + args.Channel.Id, + args.Message.Id, + args.Emoji.Name + ); + + var topic = args.Channel.Topic; + var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(topic); + if (ticketId == Guid.Empty) { + Log.Warning( + "[{Source}] Could not extract valid TicketId from channel topic. ChannelId: {ChannelId}, Topic: {Topic}", + nameof(OnMessageReactionRemovedEvent), + args.Channel.Id, + topic + ); + return; + } + + var dbContext = scope.ServiceProvider.GetRequiredService(); + + var messageEntity = await dbContext.TicketMessages.FirstOrDefaultAsync(x => + !x.SentByMod && x.BotMessageId == args.Message.Id && x.TicketId == ticketId); + if (messageEntity is null) { + Log.Debug( + "[{Source}] No matching TicketMessage found for reaction removal in ticket channel. ChannelId: {ChannelId}, MessageId: {MessageId}, TicketId: {TicketId}", + nameof(OnMessageReactionRemovedEvent), + args.Channel.Id, + args.Message.Id, + ticketId + ); + return; + } + + var ticket = await dbContext.Tickets.FirstOrDefaultAsync(x => + !x.ClosedDateUtc.HasValue && x.Id == ticketId && + x.ModMessageChannelId == args.Channel.Id); + if (ticket is null) { + Log.Warning( + "[{Source}] No active ticket found for reaction removal in ticket channel. ChannelId: {ChannelId}, MessageId: {MessageId}, TicketId: {TicketId}", + nameof(OnMessageReactionRemovedEvent), + args.Channel.Id, + args.Message.Id, + ticketId + ); + return; + } + + await RemoveReactionFromMirroredMessage( + client, + ticket.PrivateMessageChannelId, + messageEntity.MessageDiscordId, + args.Emoji + ); + } + + private static async Task RemoveReactionFromMirroredMessage( + DiscordClient client, + ulong? channelId, + ulong? messageId, + DiscordEmoji emoji + ) { + if (!channelId.HasValue || !messageId.HasValue) { + Log.Warning( + "[{Source}] Cannot remove reaction from mirrored message. Invalid ChannelId or MessageId. ChannelId: {ChannelId}, MessageId: {MessageId}, Emoji: {Emoji}", + nameof(OnMessageReactionRemovedEvent), + channelId, + messageId, + emoji.Name + ); + return; + } + + try { + var channel = await client.GetChannelAsync(channelId.Value); + var message = await channel.GetMessageAsync(messageId.Value); + await message.DeleteOwnReactionAsync(emoji); + Log.Information( + "[{Source}] Processed reaction removed {ChannelId} " + + "{MessageId} {MessageAuthor} {Emoji}", + nameof(OnMessageReactionRemovedEvent), + channel.Id, + message.Id, + message.Author?.Id, + emoji + ); + } + catch (NotFoundException) { + Log.Warning( + "[{Source}] Mirrored message not found, cannot remove reaction. ChannelId: {ChannelId}, MessageId: {MessageId}, Emoji: {Emoji}", + nameof(OnMessageReactionRemovedEvent), + channelId, + messageId, + emoji.Name + ); + } + catch (Exception ex) { + Log.Error( + ex, + "[{Source}] An error occurred while removing the reaction from the mirrored message. ChannelId: {ChannelId}, MessageId: {MessageId}, Emoji: {Emoji}", + nameof(OnMessageReactionRemovedEvent), + channelId, + messageId, + emoji.Name + ); + } + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Events/OnMessageUpdatedEvent.cs b/src/Modmail.NET/Events/OnMessageUpdatedEvent.cs new file mode 100644 index 00000000..de77b12e --- /dev/null +++ b/src/Modmail.NET/Events/OnMessageUpdatedEvent.cs @@ -0,0 +1,241 @@ +using DSharpPlus; +using DSharpPlus.Entities; +using DSharpPlus.EventArgs; +using DSharpPlus.Exceptions; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Modmail.NET.Aspects; +using Modmail.NET.Database; +using Modmail.NET.Features.UserInfo; +using Modmail.NET.Utils; +using Serilog; + +namespace Modmail.NET.Events; + +public static class OnMessageUpdatedEvent +{ + [PerformanceLoggerAspect] + public static async Task OnMessageUpdated( + DiscordClient client, + MessageUpdatedEventArgs args + ) { + if (args is null) { + Log.Debug( + "[{Source}] MessageUpdatedEventArgs is null, exiting", + nameof(OnMessageUpdatedEvent) + ); + return; + } + + if (args.Author.IsBot) { + Log.Debug( + "[{Source}] Ignoring message update from bot. UserId: {UserId}, MessageId: {MessageId}", + nameof(OnMessageUpdatedEvent), + args.Author.Id, + args.Message.Id + ); + return; + } + + if (args.MessageBefore?.Content == args.Message.Content) { + Log.Debug( + "[{Source}] Message content unchanged, ignoring update. MessageId: {MessageId}", + nameof(OnMessageUpdatedEvent), + args.Message.Id + ); + return; + } + + using var scope = client.ServiceProvider.CreateScope(); + var sender = scope.ServiceProvider.GetRequiredService(); + await sender.Send(new UpdateDiscordUserCommand(args.Author)); + + if (args.Channel.IsPrivate) + await ProcessPrivateChannelMessageUpdate(client, scope, args); + else + await ProcessTicketChannelMessageUpdate(client, scope, args); + } + + private static async Task ProcessPrivateChannelMessageUpdate( + DiscordClient client, + IServiceScope scope, + MessageUpdatedEventArgs args + ) { + Log.Debug( + "[{Source}] Processing message update in private channel. ChannelId: {ChannelId}, MessageId: {MessageId}", + nameof(OnMessageUpdatedEvent), + args.Channel.Id, + args.Message.Id + ); + + if (args.Message.Author?.Id == client.CurrentUser.Id) { + Log.Debug( + "[{Source}] Ignoring message update on bot's own message in private channel. ChannelId: {ChannelId}, MessageId: {MessageId}", + nameof(OnMessageUpdatedEvent), + args.Channel.Id, + args.Message.Id + ); + return; + } + + var dbContext = scope.ServiceProvider.GetRequiredService(); + var messageEntity = await dbContext.TicketMessages + .FirstOrDefaultAsync(x => + !x.SentByMod && x.MessageDiscordId == args.Message.Id && + x.SenderUserId == args.Message.Author.Id); + if (messageEntity is null) { + Log.Debug( + "[{Source}] No matching TicketMessage found for update in private channel. ChannelId: {ChannelId}, MessageId: {MessageId}", + nameof(OnMessageUpdatedEvent), + args.Channel.Id, + args.Message.Id + ); + return; + } + + var ticket = await dbContext.Tickets.FirstOrDefaultAsync(x => + !x.ClosedDateUtc.HasValue && x.Id == messageEntity.TicketId && + x.PrivateMessageChannelId == args.Channel.Id); + if (ticket is null) { + Log.Warning( + "[{Source}] No active ticket found for update in private channel. ChannelId: {ChannelId}, MessageId: {MessageId}, TicketId: {TicketId}", + nameof(OnMessageUpdatedEvent), + args.Channel.Id, + args.Message.Id, + messageEntity.TicketId + ); + return; + } + + await UpdateMirroredMessage( + client, + ticket.ModMessageChannelId, + messageEntity.BotMessageId, + args.MessageBefore, + args.Message + ); + } + + private static async Task ProcessTicketChannelMessageUpdate( + DiscordClient client, + IServiceScope scope, + MessageUpdatedEventArgs args + ) { + Log.Debug( + "[{Source}] Processing message update in ticket channel. ChannelId: {ChannelId}, MessageId: {MessageId}", + nameof(OnMessageUpdatedEvent), + args.Channel.Id, + args.Message.Id + ); + + var topic = args.Channel.Topic; + var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(topic); + if (ticketId == Guid.Empty) { + Log.Warning( + "[{Source}] Could not extract valid TicketId from channel topic. ChannelId: {ChannelId}, Topic: {Topic}", + nameof(OnMessageUpdatedEvent), + args.Channel.Id, + topic + ); + return; + } + + var dbContext = scope.ServiceProvider.GetRequiredService(); + + var messageEntity = await dbContext.TicketMessages.FirstOrDefaultAsync(x => + x.SentByMod && x.MessageDiscordId == args.Message.Id && + x.SenderUserId == args.Message.Author.Id); + if (messageEntity is null) { + Log.Debug( + "[{Source}] No matching TicketMessage found for update in ticket channel. ChannelId: {ChannelId}, MessageId: {MessageId}, TicketId: {TicketId}", + nameof(OnMessageUpdatedEvent), + args.Channel.Id, + args.Message.Id, + ticketId + ); + return; + } + + var ticket = await dbContext.Tickets.FirstOrDefaultAsync(x => + !x.ClosedDateUtc.HasValue && x.Id == ticketId && + x.ModMessageChannelId == args.Channel.Id); + if (ticket is null) { + Log.Warning( + "[{Source}] No active ticket found for update in ticket channel. ChannelId: {ChannelId}, MessageId: {MessageId}, TicketId: {TicketId}", + nameof(OnMessageUpdatedEvent), + args.Channel.Id, + args.Message.Id, + ticketId + ); + return; + } + + await UpdateMirroredMessage( + client, + ticket.PrivateMessageChannelId, + messageEntity.BotMessageId, + args.MessageBefore, + args.Message + ); + } + + private static async Task UpdateMirroredMessage( + DiscordClient client, + ulong? channelId, + ulong? messageId, + DiscordMessage oldMessage, + DiscordMessage updatedMessage + ) { + if (!channelId.HasValue || !messageId.HasValue) { + Log.Warning( + "[{Source}] Cannot update mirrored message. Invalid ChannelId or MessageId. ChannelId: {ChannelId}, MessageId: {MessageId}", + nameof(OnMessageUpdatedEvent), + channelId, + messageId + ); + return; + } + + try { + var channel = await client.GetChannelAsync(channelId.Value); + var message = await channel.GetMessageAsync(messageId.Value); + var embed = TicketResponses.MessageEdited(updatedMessage); + + //TODO: Add support for removing attachment files from message on message update event + //Currently the library does not support removing single attachments, either we have to remove all of them + //Or only remove if new message has zero attachemnts but if it went from 4 to 3 then we do nothing + //The only workaround for this is to upload remaining files to discord again which is unnecessary work + await message.ModifyAsync(x => { + x.ClearEmbeds(); + x.AddEmbed(embed); + }); + + Log.Information( + "[{Source}] Processed message edited {ChannelId} " + + "{MessageId} {MessageAuthor}", + nameof(OnMessageUpdatedEvent), + channel.Id, + message.Id, + updatedMessage.Author?.Id + ); + } + catch (NotFoundException) { + Log.Warning( + "[{Source}] Mirrored message not found, cannot update. ChannelId: {ChannelId}, MessageId: {MessageId}", + nameof(OnMessageUpdatedEvent), + channelId, + messageId + ); + } + catch (Exception ex) { + Log.Error( + ex, + "[{Source}] An error occurred while updating the mirrored message. ChannelId: {ChannelId}, MessageId: {MessageId}", + nameof(OnMessageUpdatedEvent), + channelId, + messageId + ); + } + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Events/UserUpdateEvents.cs b/src/Modmail.NET/Events/UserUpdateEvents.cs new file mode 100644 index 00000000..60ce3062 --- /dev/null +++ b/src/Modmail.NET/Events/UserUpdateEvents.cs @@ -0,0 +1,45 @@ +using DSharpPlus; +using DSharpPlus.Entities; +using DSharpPlus.EventArgs; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using Modmail.NET.Features.UserInfo; + +namespace Modmail.NET.Events; + +public static class UserUpdateEvents +{ + public static async Task UpdateUser(DiscordClient client, DiscordUser user) { + var scope = client.ServiceProvider.CreateScope(); + var sender = scope.ServiceProvider.GetRequiredService(); + await sender.Send(new UpdateDiscordUserCommand(user)); + } + + public static async Task InteractionCreated(DiscordClient client, InteractionCreatedEventArgs args) { + await UpdateUser(client, args.Interaction.User); + } + + public static async Task OnUserUpdated(DiscordClient client, UserUpdatedEventArgs args) { + await UpdateUser(client, args.UserAfter); + } + + public static async Task OnUserSettingsUpdated(DiscordClient client, UserSettingsUpdatedEventArgs args) { + await UpdateUser(client, args.User); + } + + public static async Task OnGuildMemberAdded(DiscordClient client, GuildMemberAddedEventArgs args) { + await UpdateUser(client, args.Member); + } + + public static async Task OnGuildMemberRemoved(DiscordClient client, GuildMemberRemovedEventArgs args) { + await UpdateUser(client, args.Member); + } + + public static async Task OnGuildBanAdded(DiscordClient client, GuildBanAddedEventArgs args) { + await UpdateUser(client, args.Member); + } + + public static async Task OnGuildBanRemoved(DiscordClient client, GuildBanRemovedEventArgs args) { + await UpdateUser(client, args.Member); + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Models/Dto/DiscordTicketMessageDto.cs b/src/Modmail.NET/Models/Dto/DiscordTicketMessageDto.cs deleted file mode 100644 index 6f68a97b..00000000 --- a/src/Modmail.NET/Models/Dto/DiscordTicketMessageDto.cs +++ /dev/null @@ -1,6 +0,0 @@ -using DSharpPlus; -using DSharpPlus.EventArgs; - -namespace Modmail.NET.Models.Dto; - -public sealed record DiscordTicketMessageDto(DiscordClient Sender, MessageCreatedEventArgs Args); \ No newline at end of file diff --git a/src/Modmail.NET/ModmailEventHandlers.cs b/src/Modmail.NET/ModmailEventHandlers.cs deleted file mode 100644 index fae561c4..00000000 --- a/src/Modmail.NET/ModmailEventHandlers.cs +++ /dev/null @@ -1,442 +0,0 @@ -using DSharpPlus; -using DSharpPlus.Entities; -using DSharpPlus.Entities.AuditLogs; -using DSharpPlus.EventArgs; -using DSharpPlus.Exceptions; -using MediatR; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Modmail.NET.Abstract; -using Modmail.NET.Aspects; -using Modmail.NET.Database; -using Modmail.NET.Features.Ticket; -using Modmail.NET.Features.UserInfo; -using Modmail.NET.Models.Dto; -using Modmail.NET.Queues; -using Modmail.NET.Utils; -using Serilog; - -namespace Modmail.NET; - -public class ModmailEventHandlers -{ - public static async Task InteractionCreated(DiscordClient client, InteractionCreatedEventArgs args) { - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - await sender.Send(new UpdateDiscordUserCommand(args?.Interaction?.User)); - } - - public static async Task OnUserUpdated(DiscordClient client, UserUpdatedEventArgs args) { - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - await sender.Send(new UpdateDiscordUserCommand(args.UserAfter)); - } - - public static async Task OnUserSettingsUpdated(DiscordClient client, UserSettingsUpdatedEventArgs args) { - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - await sender.Send(new UpdateDiscordUserCommand(args.User)); - } - - public static async Task OnMessageDeleted(DiscordClient client, MessageDeletedEventArgs args) { - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - await sender.Send(new UpdateDiscordUserCommand(args?.Message.Author)); - - if (args is not null) { - if (args.Message.Author?.IsBot == true) return; - var isPrivate = args.Channel.IsPrivate; - if (isPrivate) { - // message deleted by user in dms - var dbContext = scope.ServiceProvider.GetRequiredService(); - var messageEntity = await dbContext.TicketMessages - .Where(x => !x.SentByMod && x.MessageDiscordId == args.Message.Id) - .FirstOrDefaultAsync(); - if (messageEntity is null) return; - - var ticket = await dbContext.Tickets - .Where(x => !x.ClosedDateUtc.HasValue && x.Id == messageEntity.TicketId && x.PrivateMessageChannelId == args.Channel.Id) - .FirstOrDefaultAsync(); - if (ticket is null) return; - - try { - var ticketChannel = await client.GetChannelAsync(ticket.ModMessageChannelId); - var message = await ticketChannel.GetMessageAsync(messageEntity.BotMessageId); - await message.DeleteAsync(); - Log.Information("[OnMessageDeleted] Processed message deleted {ChannelId} {MessageId} {MessageAuthor}", ticketChannel.Id, message.Id, message.Author?.Id); - } - catch (NotFoundException) { - //ignored - } - } - else { - var topic = args.Channel.Topic; - var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(topic); - if (ticketId == Guid.Empty) return; - // message deleted by mod or other admin in ticket channel - var dbContext = scope.ServiceProvider.GetRequiredService(); - - var messageEntity = await dbContext.TicketMessages - .Where(x => x.SentByMod && x.TicketId == ticketId && x.MessageDiscordId == args.Message.Id) - .FirstOrDefaultAsync(); - if (messageEntity is null) return; - - var ticket = await dbContext.Tickets - .Where(x => !x.ClosedDateUtc.HasValue && x.Id == ticketId && x.ModMessageChannelId == args.Channel.Id) - .FirstOrDefaultAsync(); - if (ticket is null) return; - - try { - var dmChannel = await client.GetChannelAsync(ticket.PrivateMessageChannelId); - var message = await dmChannel.GetMessageAsync(messageEntity.BotMessageId); - await message.DeleteAsync(); - Log.Information("[OnMessageDeleted] Processed message deleted {ChannelId} {MessageId} {MessageAuthor}", dmChannel.Id, message.Id, message.Author?.Id); - } - catch (NotFoundException) { - //ignored - } - } - } - } - - public static async Task OnMessageReactionAdded(DiscordClient client, MessageReactionAddedEventArgs args) { - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - var bot = scope.ServiceProvider.GetRequiredService(); - await sender.Send(new UpdateDiscordUserCommand(args?.User)); - - if (args is not null) { - if (args.User.IsBot) return; - - if (args.Emoji.Name == Const.ProcessedReactionDiscordEmojiUnicode) return; - - var isPrivate = args.Channel.IsPrivate; - if (isPrivate) { - var isAuthorBot = args.Message.Author?.Id == client.CurrentUser.Id; - if (isAuthorBot) return; - - var dbContext = scope.ServiceProvider.GetRequiredService(); - var messageEntity = await dbContext.TicketMessages - .Where(x => x.SentByMod && x.BotMessageId == args.Message.Id) - .FirstOrDefaultAsync(); - if (messageEntity is null) return; - - var ticket = await dbContext.Tickets - .Where(x => !x.ClosedDateUtc.HasValue && x.OpenerUserId == args.User.Id && x.Id == messageEntity.TicketId && x.PrivateMessageChannelId == args.Channel.Id) - .FirstOrDefaultAsync(); - if (ticket is null) return; - - try { - var ticketChannel = await client.GetChannelAsync(ticket.ModMessageChannelId); - var message = await ticketChannel.GetMessageAsync(messageEntity.MessageDiscordId); - await message.CreateReactionAsync(args.Emoji); - Log.Information("[OnMessageReactionAdded] Processed reaction added {ChannelId} {MessageId} {MessageAuthor} {Emoji}", ticketChannel.Id, message.Id, message.Author?.Id, args.Emoji); - } - catch (NotFoundException) { - //ignored - } - } - else { - var topic = args.Channel.Topic; - var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(topic); - if (ticketId == Guid.Empty) return; - - var dbContext = scope.ServiceProvider.GetRequiredService(); - - var messageEntity = await dbContext.TicketMessages - .Where(x => !x.SentByMod && x.BotMessageId == args.Message.Id && x.TicketId == ticketId) - .FirstOrDefaultAsync(); - if (messageEntity is null) return; - - var ticket = await dbContext.Tickets - .Where(x => !x.ClosedDateUtc.HasValue && x.Id == ticketId && x.ModMessageChannelId == args.Channel.Id) - .FirstOrDefaultAsync(); - if (ticket is null) return; - - try { - var pmChannel = await client.GetChannelAsync(ticket.PrivateMessageChannelId); - var message = await pmChannel.GetMessageAsync(messageEntity.MessageDiscordId); - await message.CreateReactionAsync(args.Emoji); - Log.Information("[OnMessageReactionAdded] Processed reaction added {ChannelId} {MessageId} {MessageAuthor} {Emoji}", pmChannel.Id, message.Id, message.Author?.Id, args.Emoji); - } - catch (NotFoundException) { - //ignored - } - } - } - } - - public static async Task OnMessageReactionRemoved(DiscordClient client, MessageReactionRemovedEventArgs args) { - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - var bot = scope.ServiceProvider.GetRequiredService(); - await sender.Send(new UpdateDiscordUserCommand(args?.User)); - - if (args is not null) { - if (args.User.IsBot) return; - - if (args.Emoji.Name == Const.ProcessedReactionDiscordEmojiUnicode) return; - - var isPrivate = args.Channel.IsPrivate; - if (isPrivate) { - var isAuthorBot = args.Message.Author?.Id == client.CurrentUser.Id; - if (!isAuthorBot) return; - - var dbContext = scope.ServiceProvider.GetRequiredService(); - var messageEntity = await dbContext.TicketMessages - .Where(x => x.SentByMod && x.BotMessageId == args.Message.Id) - .FirstOrDefaultAsync(); - if (messageEntity is null) return; - - var ticket = await dbContext.Tickets - .Where(x => !x.ClosedDateUtc.HasValue && x.OpenerUserId == args.User.Id && x.Id == messageEntity.TicketId && x.PrivateMessageChannelId == args.Channel.Id) - .FirstOrDefaultAsync(); - if (ticket is null) return; - - try { - var ticketChannel = await client.GetChannelAsync(ticket.ModMessageChannelId); - var message = await ticketChannel.GetMessageAsync(messageEntity.MessageDiscordId); - await message.DeleteOwnReactionAsync(args.Emoji); - Log.Information("[OnMessageReactionRemoved] Processed reaction added {ChannelId} {MessageId} {MessageAuthor} {Emoji}", ticketChannel.Id, message.Id, message.Author?.Id, args.Emoji); - } - catch (NotFoundException) { - //ignored - } - } - else { - var topic = args.Channel.Topic; - var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(topic); - if (ticketId == Guid.Empty) return; - - var dbContext = scope.ServiceProvider.GetRequiredService(); - - var messageEntity = await dbContext.TicketMessages - .Where(x => !x.SentByMod && x.BotMessageId == args.Message.Id && x.TicketId == ticketId) - .FirstOrDefaultAsync(); - if (messageEntity is null) return; - - var ticket = await dbContext.Tickets - .Where(x => !x.ClosedDateUtc.HasValue && x.Id == ticketId && x.ModMessageChannelId == args.Channel.Id) - .FirstOrDefaultAsync(); - if (ticket is null) return; - - try { - var pmChannel = await client.GetChannelAsync(ticket.PrivateMessageChannelId); - var message = await pmChannel.GetMessageAsync(messageEntity.MessageDiscordId); - await message.DeleteOwnReactionAsync(args.Emoji); - Log.Information("[OnMessageReactionRemoved] Processed reaction added {ChannelId} {MessageId} {MessageAuthor} {Emoji}", pmChannel.Id, message.Id, message.Author?.Id, args.Emoji); - } - catch (NotFoundException) { - //ignored - } - } - } - } - - public static async Task OnMessageUpdated(DiscordClient client, MessageUpdatedEventArgs args) { - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - await sender.Send(new UpdateDiscordUserCommand(args?.Author)); - } - - public static async Task OnGuildMemberAdded(DiscordClient client, GuildMemberAddedEventArgs args) { - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - await sender.Send(new UpdateDiscordUserCommand(args?.Member)); - } - - public static async Task OnGuildMemberRemoved(DiscordClient client, GuildMemberRemovedEventArgs args) { - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - await sender.Send(new UpdateDiscordUserCommand(args?.Member)); - } - - public static async Task OnMessageCreated(DiscordClient client, MessageCreatedEventArgs args) { - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - await sender.Send(new UpdateDiscordUserCommand(args.Message.Author)); - if (args.Message.Author?.IsBot == true) return; - if (args.Message.IsTTS) return; - - var ticketMessageQueue = scope.ServiceProvider.GetRequiredService(); - await ticketMessageQueue.Enqueue(args.Author.Id, new DiscordTicketMessageDto(client, args)); - } - - public static async Task OnGuildBanAdded(DiscordClient client, GuildBanAddedEventArgs args) { - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - await sender.Send(new UpdateDiscordUserCommand(args.Member)); - } - - public static async Task OnGuildBanRemoved(DiscordClient client, GuildBanRemovedEventArgs args) { - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - await sender.Send(new UpdateDiscordUserCommand(args.Member)); - } - - [PerformanceLoggerAspect] - public static async Task OnChannelDeleted(DiscordClient client, ChannelDeletedEventArgs args) { - const string logMessage = $"[{nameof(OnChannelDeleted)}]({{ChannelId}})"; - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - var langData = scope.ServiceProvider.GetRequiredService(); - var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(args.Channel.Topic); - if (ticketId != Guid.Empty) - try { - var auditLogEntry = await args.Guild.GetAuditLogsAsync(1, null, DiscordAuditLogActionType.ChannelDelete).FirstOrDefaultAsync(); - var user = auditLogEntry?.UserResponsible ?? client.CurrentUser; - await sender.Send(new UpdateDiscordUserCommand(user)); - var ticket = await sender.Send(new GetTicketQuery(ticketId, true)); - if (ticket is not null) { - if (ticket.ClosedDateUtc.HasValue) return; // Ticket is already closed - await sender.Send(new ProcessCloseTicketCommand(ticketId, user.Id, langData.GetTranslation(LangKeys.ChannelWasDeleted), args.Channel)); - Log.Information(logMessage, args.Channel.Id); - } - } - catch (BotExceptionBase ex) { - Log.Warning(ex, logMessage, args.Channel.Id); - } - catch (Exception ex) { - Log.Error(ex, logMessage, args.Channel.Id); - } - } - - - [PerformanceLoggerAspect] - public static async Task ComponentInteractionCreated(DiscordClient client, ComponentInteractionCreatedEventArgs args) { - const string logMessage = $"[{nameof(ComponentInteractionCreated)}]({{CustomId}},{{UserId}},{{ChannelId}},{{InteractionId}},{{MessageId}})"; - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - - try { - await sender.Send(new UpdateDiscordUserCommand(args.User)); - var interaction = args.Interaction; - var key = interaction?.Data?.CustomId; - var (interactionName, parameters) = UtilInteraction.ParseKey(key); - var messageId = args.Message.Id; - - switch (interactionName) { - case "star": { - //feedback process show modal - var starParam = parameters[0]; - var ticketIdParam = parameters[1]; - - var starCount = int.Parse(starParam); - var ticketId = Guid.Parse(ticketIdParam); - - var feedbackModal = Modals.CreateFeedbackModal(starCount, ticketId, messageId); - - await args.Interaction.CreateResponseAsync(DiscordInteractionResponseType.Modal, feedbackModal); - Log.Information(logMessage, - args.Interaction?.Data?.CustomId, - args.User?.Id, - args.Channel?.Id, - args.Interaction?.Id, - args.Message?.Id); - break; - } - case "ticket_type": { - //for user ticket change type allowed only once for user - await args.Interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage); - var ticketIdParam = parameters[0]; - var ticketId = Guid.Parse(ticketIdParam); - var selectedTypeKey = args.Values.FirstOrDefault(); - if (string.IsNullOrEmpty(selectedTypeKey)) break; - - await sender.Send(new ProcessChangeTicketTypeCommand(ticketId, selectedTypeKey, null, args.Channel, args.Message, args.User.Id)); - Log.Information(logMessage, - args.Interaction?.Data?.CustomId, - args.User?.Id, - args.Channel?.Id, - args.Interaction?.Id, - args.Message?.Id); - break; - } - case "close_ticket": // This must stay due to deprecation and support for existing tickets (v2.0 beta) - case "close_ticket_with_reason": { - var ticketIdParam = parameters[0]; - var ticketId = Guid.Parse(ticketIdParam); - var modal = Modals.CreateCloseTicketWithReasonModal(ticketId); - await args.Interaction.CreateResponseAsync(DiscordInteractionResponseType.Modal, modal); - - Log.Information(logMessage, - args.Interaction?.Data?.CustomId, - args.User?.Id, - args.Channel?.Id, - args.Interaction?.Id, - args.Message?.Id); - break; - } - } - } - - - catch (BotExceptionBase ex) { - Log.Warning(ex, - logMessage, - args.Interaction?.Data?.CustomId, - args.User?.Id, - args.Channel?.Id, - args.Interaction?.Id, - args.Message?.Id); - } - catch (Exception ex) { - Log.Fatal(ex, - logMessage, - args.Interaction?.Data?.CustomId, - args.User?.Id, - args.Channel?.Id, - args.Interaction?.Id, - args.Message?.Id); - } - } - - [PerformanceLoggerAspect] - public static async Task ModalSubmitted(DiscordClient client, ModalSubmittedEventArgs args) { - const string logMessage = $"[{nameof(ModalSubmitted)}]({{CustomId}},{{InteractionId}})"; - await args.Interaction.CreateResponseAsync(DiscordInteractionResponseType.ChannelMessageWithSource, - new DiscordInteractionResponseBuilder().AsEphemeral().WithContent(LangProvider.This.GetTranslation(LangKeys.ThankYouForFeedback))); - var scope = client.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - try { - await sender.Send(new UpdateDiscordUserCommand(args.Interaction.User)); - - // var interaction = args.Interaction; - var id = args.Interaction.Data.CustomId; - var (interactionName, parameters) = UtilInteraction.ParseKey(id); - switch (interactionName) { - case "feedback": { - var textInput = args.Values["feedback"]; - - var starParam = parameters[0]; - var ticketIdParam = parameters[1]; - var messageIdParam = parameters[2]; - - var starCount = int.Parse(starParam); - var ticketId = Guid.Parse(ticketIdParam); - var feedbackMessageId = ulong.Parse(messageIdParam); - - var feedbackMessage = await args.Interaction.Channel.GetMessageAsync(feedbackMessageId); - await sender.Send(new ProcessAddFeedbackCommand(ticketId, starCount, textInput, feedbackMessage)); - Log.Information(logMessage, args.Interaction.Data.CustomId, args.Interaction.Id); - break; - } - case "close_ticket_with_reason": { - var textInput = args.Values["reason"]; - var ticketIdParam = parameters[0]; - var ticketId = Guid.Parse(ticketIdParam); - await sender.Send(new ProcessCloseTicketCommand(ticketId, args.Interaction.User.Id, textInput, args.Interaction.Channel)); - Log.Information(logMessage, args.Interaction.Data.CustomId, args.Interaction.Id); - break; - } - } - } - - catch (BotExceptionBase ex) { - Log.Warning(ex, logMessage, args.Interaction.Data.CustomId, args.Interaction.Id); - } - catch (Exception ex) { - Log.Error(ex, logMessage, args.Interaction.Data.CustomId, args.Interaction.Id); - } - } -} \ No newline at end of file From 1467181f4364d0a4aa20a01b4e3947546a8fa50d Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 17:10:49 +0300 Subject: [PATCH 15/47] chore: updated DSharp plus latest nuget nightly version --- src/Modmail.NET/Modmail.NET.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Modmail.NET/Modmail.NET.csproj b/src/Modmail.NET/Modmail.NET.csproj index 63bdd31d..7099dca8 100644 --- a/src/Modmail.NET/Modmail.NET.csproj +++ b/src/Modmail.NET/Modmail.NET.csproj @@ -17,9 +17,9 @@ - - - + + + From 5702bc52bb6ab64575c76d667e7840d07ce5c0c7 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:02:26 +0300 Subject: [PATCH 16/47] feat: Improve project structure with vertical slicing and granular command/query files - Heavily reorganized the project structure to better follow vertical slice architecture principles. - Enhanced code discoverability by separating monolithic Queries.cs and Commands.cs files into individual files, each representing a single command or query. - Includes minor class renamings for improved clarity and consistency. --- .../Components/App.razor | 2 +- .../Components/Layout/AuthLayout.razor | 6 +- .../Components/Layout/Shared/AppFooter.razor | 2 +- .../Components/Pages/Blacklist.razor | 11 +- .../Components/Pages/Dashboard.razor | 19 +- .../Components/Pages/Options.razor | 31 ++- .../Components/Pages/Setup.razor | 4 +- .../Components/Pages/Teams.razor | 11 +- .../Components/Pages/TicketTypes.razor | 11 +- .../Components/Pages/Tickets.razor | 25 +- .../Components/Pages/Transcript.razor | 5 +- .../Components/Shared/AccountDialog.razor | 2 +- .../Shared/Blacklist/AddBlacklistDialog.razor | 9 +- .../Shared/Teams/AddRoleToTeamDialog.razor | 13 +- .../Shared/Teams/AddUserToTeamDialog.razor | 11 +- .../Teams/CreateOrUpdateTeamDialog.razor | 13 +- .../Shared/Teams/TeamDetailsDialog.razor | 13 +- .../CreateOrUpdateTicketTypeDialog.razor | 9 +- .../Shared/Tickets/TicketNotes.razor | 3 +- .../Components/_Imports.razor | 5 - .../Controllers/AttachmentController.cs | 4 +- .../Dependency/AuthDependency.cs | 8 +- .../Dependency/BlazorDependency.cs | 2 +- .../Dependency/BusinessDependency.cs | 7 +- .../Dependency/DiscordBotDependency.cs | 5 +- .../Dependency/HangfireDependency.cs | 2 +- .../Extensions/WebExtensions.cs} | 4 +- src/Modmail.NET.Web.Blazor/Program.cs | 2 +- .../Providers/AuthorizeTeamAttribute.cs | 3 +- .../HangfireAuthorizationProvider.cs | 2 +- .../Providers/TeamPermissionCheckHandler.cs | 6 +- .../TeamPermissionCheckRequirement.cs | 2 +- src/Modmail.NET.Web.Blazor/RadzenTools.cs | 4 +- src/Modmail.NET/Abstract/BotExceptionBase.cs | 26 -- src/Modmail.NET/Abstract/IEntity.cs | 3 - .../{BaseQueue.cs => MemoryQueueBase.cs} | 6 +- src/Modmail.NET/BotConfig.cs | 1 + .../Aspects/PerformanceLoggerAspect.cs | 2 +- .../AnotherServerAlreadySetupException.cs | 8 + ...eteTicketTypeWhenActiveTicketsException.cs | 6 +- .../Common/Exceptions/DbInternalException.cs | 8 + .../Exceptions/EmptyListResultException.cs | 6 +- .../InvalidInteractionKeyException.cs | 8 + .../Exceptions/InvalidMessageIdException.cs | 8 + .../Exceptions/InvalidNameException.cs | 6 +- .../Exceptions/InvalidUserIdException.cs | 8 + .../MainServerAlreadySetupException.cs | 8 + .../MemberAlreadyInTeamException.cs | 8 + .../Common/Exceptions/ModmailBotException.cs | 27 ++ .../Exceptions/NotFoundException.cs | 6 +- .../Exceptions/NotFoundInException.cs | 6 +- .../Exceptions/NotFoundWithException.cs | 6 +- .../NotJoinedMainServerException.cs | 8 + .../Exceptions/RoleAlreadyInTeamException.cs | 8 + .../Exceptions/ServerIsNotSetupException.cs | 8 + .../Exceptions/TeamAlreadyExistsException.cs | 8 + .../Exceptions/TeamNotExistsException.cs | 8 + .../TicketAlreadyClosedException.cs | 8 + .../Exceptions/TicketMustBeClosedException.cs | 8 + .../TicketTimeoutOutOfRangeException.cs | 11 + .../TicketTypeAlreadyExistsException.cs | 6 +- .../TicketTypeNotExistsException.cs | 6 +- .../UserAlreadyBlacklistedException.cs | 8 + .../UserIsNotBlacklistedException.cs | 8 + .../Extensions/DiscordUserExtensions.cs} | 4 +- .../Extensions/EmbedExtensions.cs} | 10 +- .../Common/Extensions/ExceptionExtensions.cs | 45 ++++ .../Extensions/StringExtensions.cs} | 4 +- .../{ => Common}/Static/AuthPolicy.cs | 2 +- src/Modmail.NET/Common/Static/Const.cs | 10 + .../{ => Common}/Static/DbLength.cs | 2 +- src/Modmail.NET/{ => Common}/Static/DbType.cs | 2 +- .../{ => Common}/Static/EnvironmentType.cs | 2 +- .../{ => Common}/Static/HangfireQueueName.cs | 2 +- .../Static/ModmailColors.cs} | 4 +- .../Static/ModmailEmbeds.cs} | 12 +- .../Common/Static/ModmailInteractions.cs | 22 ++ .../Common/Static/ModmailWebhooks.cs | 22 ++ .../{ => Common}/Utils/UtilAttachment.cs | 11 +- .../{ => Common}/Utils/UtilCache.cs | 2 +- .../{ => Common}/Utils/UtilChannelTopic.cs | 2 +- .../{ => Common}/Utils/UtilDate.cs | 2 +- .../{ => Common}/Utils/UtilHash.cs | 2 +- .../{ => Common}/Utils/UtilInteraction.cs | 4 +- .../{ => Common}/Utils/UtilMention.cs | 4 +- .../{ => Common}/Utils/UtilPermission.cs | 5 +- .../{ => Common}/Utils/UtilReadable.cs | 2 +- .../{ => Common}/Utils/UtilVersion.cs | 2 +- src/Modmail.NET/Database/Abstract/IEntity.cs | 3 + .../{ => Database}/Abstract/IGuidId.cs | 2 +- .../Abstract/IHasRegisterDate.cs | 2 +- .../{ => Database}/Abstract/IHasUpdateDate.cs | 2 +- .../DiscordUserInfoConfiguration.cs | 2 +- .../Configuration/GuildOptionConfiguration.cs | 2 +- .../Configuration/GuildTeamConfiguration.cs | 2 +- .../GuildTeamMemberConfiguration.cs | 2 +- .../TicketBlacklistConfiguration.cs | 2 +- .../Configuration/TicketConfiguration.cs | 2 +- .../TicketMessageAttachmentConfiguration.cs | 2 +- .../TicketMessageConfiguration.cs | 2 +- .../Configuration/TicketNoteConfiguration.cs | 2 +- .../Configuration/TicketTypeConfiguration.cs | 2 +- .../Entities/DiscordUserInfo.cs | 7 +- .../{ => Database}/Entities/GuildOption.cs | 16 +- .../{ => Database}/Entities/GuildTeam.cs | 6 +- .../Entities/GuildTeamMember.cs | 5 +- .../{ => Database}/Entities/Statistic.cs | 4 +- .../{ => Database}/Entities/Ticket.cs | 8 +- .../Entities/TicketBlacklist.cs | 5 +- .../{ => Database}/Entities/TicketMessage.cs | 9 +- .../Entities/TicketMessageAttachment.cs | 5 +- .../{ => Database}/Entities/TicketNote.cs | 5 +- .../{ => Database}/Entities/TicketType.cs | 5 +- .../20240827120247_Initial.Designer.cs | 0 .../Migrations/20240827120247_Initial.cs | 0 ...0827121750_Ticket_AssignedUser.Designer.cs | 0 .../20240827121750_Ticket_AssignedUser.cs | 0 ...oved_GreetingAndClosingMessage.Designer.cs | 0 ...ption_Removed_GreetingAndClosingMessage.cs | 0 ...20240828104105_RemovedUnusedFK.Designer.cs | 0 .../20240828104105_RemovedUnusedFK.cs | 0 ...amMember_Removed_UpdateDateUtc.Designer.cs | 0 ...6_GuildTeamMember_Removed_UpdateDateUtc.cs | 0 ...906_TicketType_Added_InEnabled.Designer.cs | 0 ...240901191906_TicketType_Added_InEnabled.cs | 0 ...etType_Updated_EmbedIsNullable.Designer.cs | 0 ...2958_TicketType_Updated_EmbedIsNullable.cs | 0 ...e_Update_DescriptionIsRequired.Designer.cs | 0 ...TicketType_Update_DescriptionIsRequired.cs | 0 ..._IsEnableDiscordChannelLogging.Designer.cs | 0 ..._AddedCol_IsEnableDiscordChannelLogging.cs | 0 ...on_AddedColumn_AlwaysAnonymous.Designer.cs | 0 ...GuildOption_AddedColumn_AlwaysAnonymous.cs | 0 ...ded_SlashCommandDisableColumns.Designer.cs | 0 ..._ColumnAdded_SlashCommandDisableColumns.cs | 0 ...Added_AllowUsersToCloseTickets.Designer.cs | 0 ...ption_ColAdded_AllowUsersToCloseTickets.cs | 0 ...ption_AddedAvarageResponseTime.Designer.cs | 0 ...09_GuildOption_AddedAvarageResponseTime.cs | 0 ..._GuildOption_AddedCols_AvgData.Designer.cs | 0 ...319030317_GuildOption_AddedCols_AvgData.cs | 0 ...Attachment_ByteContent_Removed.Designer.cs | 0 ...etMessageAttachment_ByteContent_Removed.cs | 0 ...Option_IconUrl_Nullable_BugFix.Designer.cs | 0 ...005_GuildOption_IconUrl_Nullable_BugFix.cs | 0 ...GuildOption_RemovedSomeColumns.Designer.cs | 0 ...22125230_GuildOption_RemovedSomeColumns.cs | 0 ...ption_AddedCol_PermissionLevel.Designer.cs | 0 ...29_GuildOption_AddedCol_PermissionLevel.cs | 0 ...TicketMessageSentByModColAdded.Designer.cs | 0 ...22225844_TicketMessageSentByModColAdded.cs | 0 ...0323013715_StatisticTableAdded.Designer.cs | 0 .../20250323013715_StatisticTableAdded.cs | 0 ...mAddedColAllowAccessToWebPanel.Designer.cs | 0 ..._GuildTeamAddedColAllowAccessToWebPanel.cs | 0 ...ldOption_RemovedFeatureOptions.Designer.cs | 0 ...92315_GuildOption_RemovedFeatureOptions.cs | 0 ...ionRemoveOptionPermAccessLevel.Designer.cs | 0 ..._GuildOptionRemoveOptionPermAccessLevel.cs | 0 ...32054_StatisticUpdatedColNames.Designer.cs | 0 ...20250326132054_StatisticUpdatedColNames.cs | 0 ...StatisticRenameResolvedTickets.Designer.cs | 0 ...26235413_StatisticRenameResolvedTickets.cs | 0 ...ovedShowConfirmationOnCloseCol.Designer.cs | 0 ...OptionRemovedShowConfirmationOnCloseCol.cs | 0 ...7_GuildOptionPublicTranscripts.Designer.cs | 0 ...0329204247_GuildOptionPublicTranscripts.cs | 0 ...MessageLengthExtendedToNoLimit.Designer.cs | 0 ...18_TicketMessageLengthExtendedToNoLimit.cs | 0 ...ndTranscriptLinkToUserColAdded.Designer.cs | 0 ...dOptionSendTranscriptLinkToUserColAdded.cs | 0 ...MessageAddedColumnBotMessageId.Designer.cs | 0 ...08_TicketMessageAddedColumnBotMessageId.cs | 0 ...dedColTicketAutoDeleteWaitDays.Designer.cs | 0 ...dOptionAddedColTicketAutoDeleteWaitDays.cs | 0 .../20250406180016_Constraints.Designer.cs | 0 .../Migrations/20250406180016_Constraints.cs | 0 ...ddedColStatisticsCalculateDays.Designer.cs | 0 ...ldOptionAddedColStatisticsCalculateDays.cs | 4 +- ...ldOptionConstraintFixMinValues.Designer.cs | 0 ...81350_GuildOptionConstraintFixMinValues.cs | 0 .../ModmailDbContextModelSnapshot.cs | 0 src/Modmail.NET/Database/ModmailDbContext.cs | 2 +- .../Database/Triggers/IdentityV7Trigger.cs | 2 +- .../Database/Triggers/RegisterDateTrigger.cs | 4 +- .../Database/Triggers/UpdateDateTrigger.cs | 4 +- .../AnotherServerAlreadySetupException.cs | 8 - .../Exceptions/DbInternalException.cs | 8 - .../InvalidInteractionKeyException.cs | 8 - .../Exceptions/InvalidMessageIdException.cs | 8 - .../Exceptions/InvalidUserIdException.cs | 8 - .../MainServerAlreadySetupException.cs | 8 - .../MemberAlreadyInTeamException.cs | 8 - .../NotJoinedMainServerException.cs | 8 - .../Exceptions/RoleAlreadyInTeamException.cs | 8 - .../Exceptions/ServerIsNotSetupException.cs | 8 - .../Exceptions/TeamAlreadyExistsException.cs | 8 - .../Exceptions/TeamNotExistsException.cs | 8 - .../TicketAlreadyClosedException.cs | 8 - .../Exceptions/TicketMustBeClosedException.cs | 8 - .../TicketTimeoutOutOfRangeException.cs | 10 - .../UserAlreadyBlacklistedException.cs | 8 - .../UserIsNotBlacklistedException.cs | 8 - src/Modmail.NET/Extensions/ExtException.cs | 43 ---- .../ProcessAddUserToBlacklistCommand.cs} | 11 +- .../ProcessRemoveUserFromBlacklistCommand.cs | 11 + .../CheckUserBlacklistStatusHandler.cs | 3 +- .../Handlers/GetAllBlacklistHandler.cs | 6 +- .../Blacklist/Handlers/GetBlacklistHandler.cs | 5 +- .../ProcessAddUserToBlacklistHandler.cs | 22 +- .../ProcessRemoveUserFromBlacklistHandler.cs | 16 +- src/Modmail.NET/Features/Blacklist/Queries.cs | 19 -- .../Queries/CheckUserBlacklistStatusQuery.cs | 5 + .../Blacklist/Queries/GetAllBlacklistQuery.cs | 11 + .../Blacklist/Queries/GetBlacklistQuery.cs | 14 ++ .../Blacklist/Static/BlacklistBotMessages.cs | 34 +++ src/Modmail.NET/Features/Bot/Queries.cs | 13 - .../ComponentInteractionCreatedEvent.cs | 21 +- .../DiscordBot}/Events/ModalSubmittedEvent.cs | 17 +- .../Events/OnChannelDeletedEvent.cs | 18 +- .../Events/OnMessageCreatedEvent.cs | 6 +- .../Events/OnMessageDeletedEvent.cs | 6 +- .../Events/OnMessageReactionAddedEvent.cs | 11 +- .../Events/OnMessageReactionRemovedEvent.cs | 11 +- .../Events/OnMessageUpdatedEvent.cs | 11 +- .../DiscordBot}/Events/UserUpdateEvents.cs | 4 +- .../Handlers/GetDiscordLogChannelHandler.cs | 6 +- .../Handlers/GetDiscordMainGuildHandler.cs | 12 +- .../Handlers/GetDiscordMemberHandler.cs | 5 +- .../Queries/GetDiscordLogChannelQuery.cs | 8 + .../Queries/GetDiscordMainGuildQuery.cs | 8 + .../Queries/GetDiscordMemberQuery.cs | 6 + .../Attributes/RequireMainServerAttribute.cs | 2 +- ...RequirePermissionLevelOrHigherAttribute.cs | 3 +- .../RequireTicketChannelAttribute.cs | 2 +- .../UpdateUserInformationAttribute.cs | 2 +- .../Checks/RequireMainServerCheck.cs | 5 +- .../RequirePermissionLevelOrHigherCheck.cs | 7 +- .../Checks/RequireTicketChannelCheck.cs | 7 +- .../Checks/UpdateUserInformationCheck.cs | 6 +- .../Handlers}/BlacklistSlashCommands.cs | 36 +-- .../Handlers}/ModmailCommands.cs | 18 +- .../Handlers}/TicketSlashCommands.cs | 52 ++-- .../Helpers}/TicketTypeProvider.cs | 4 +- src/Modmail.NET/Features/Guild/Commands.cs | 19 -- .../Guild/Commands/ClearGuildOptionCommand.cs | 10 + .../ProcessCreateLogChannelCommand.cs | 11 + .../Commands/ProcessGuildSetupCommand.cs | 12 + .../Handlers/CheckAnyGuildSetupHandler.cs | 1 + .../Guild/Handlers/ClearGuildOptionHandler.cs | 1 + .../Guild/Handlers/GetGuildOptionHandler.cs | 5 +- .../ProcessCreateLogChannelHandlers.cs | 18 +- .../Handlers/ProcessGuildSetupHandler.cs | 10 +- .../Guild/Queries/CheckAnyGuildSetupQuery.cs | 5 + .../GetGuildOptionQuery.cs} | 8 +- .../Features/Guild/Static/GuildConstants.cs | 7 + src/Modmail.NET/Features/Metric/Commands.cs | 0 .../Metric/Handlers/GetLatestMetricHandler.cs | 7 +- .../Metric}/Jobs/StatisticsCalculatorJob.cs | 13 +- .../Metric/Models}/ChartItemDto.cs | 2 +- .../Metric/Models}/MetricDto.cs | 4 +- .../GetLatestMetricQuery.cs} | 4 +- .../Features/Metric/Static/MetricConstants.cs | 8 + .../Handler/CheckPermissionAccessHandler.cs | 7 +- .../Handler/CheckRoleInAnyTeamHandler.cs | 2 + .../Handler/GetPermissionInfoHandler.cs | 3 +- .../GetPermissionInfoOrHigherHandler.cs | 3 +- .../Handler/GetPermissionLevelHandler.cs | 5 +- .../Permission}/Models/PermissionInfo.cs | 5 +- .../Features/Permission/Queries.cs | 16 -- .../Queries/CheckPermissionAccessQuery.cs | 6 + .../Queries/CheckRoleInAnyTeamQuery.cs | 5 + .../Queries/CheckUserInAnyTeamQuery.cs | 5 + .../Queries/GetPermissionInfoOrHigherQuery.cs | 7 + .../Queries/GetPermissionInfoQuery.cs | 6 + .../Queries/GetPermissionLevelQuery.cs | 6 + .../Permission}/Static/TeamPermissionLevel.cs | 2 +- src/Modmail.NET/Features/Teams/Commands.cs | 64 ----- .../Commands/ProcessAddRoleToTeamCommand.cs | 14 ++ .../Commands/ProcessAddTeamMemberCommand.cs | 13 + .../Commands/ProcessCreateTeamCommand.cs | 19 ++ .../Commands/ProcessRemoveTeamCommand.cs | 13 + .../ProcessRemoveTeamMemberCommand.cs | 14 ++ .../Commands/ProcessRenameTeamCommand.cs | 13 + .../Commands/ProcessUpdateTeamCommand.cs | 19 ++ .../Teams/Handlers/CheckTeamExistsHandler.cs | 1 + .../Handlers/CheckUserInAnyTeamHandler.cs | 3 +- .../Teams/Handlers/GetTeamByNameHandler.cs | 6 +- .../Features/Teams/Handlers/GetTeamHandler.cs | 5 +- .../Teams/Handlers/GetTeamListHandler.cs | 6 +- .../Handlers/ProcessAddRoleToTeamHandler.cs | 10 +- .../Handlers/ProcessAddTeamMemberHandler.cs | 11 +- .../Handlers/ProcessCreateTeamHandler.cs | 9 +- .../Handlers/ProcessRemoveTeamHandler.cs | 6 +- .../ProcessRemoveTeamMemberHandler.cs | 4 +- .../Handlers/ProcessRenameTeamHandler.cs | 4 +- .../Handlers/ProcessUpdateTeamHandler.cs | 4 +- src/Modmail.NET/Features/Teams/Queries.cs | 19 -- .../Teams/Queries/CheckTeamExistsQuery.cs | 10 + .../Teams/Queries/GetTeamByNameQuery.cs | 9 + .../Teams/Queries/GetTeamListQuery.cs | 9 + .../Features/Teams/Queries/GetTeamQuery.cs | 9 + .../Teams}/Static/TeamMemberDataType.cs | 2 +- src/Modmail.NET/Features/Ticket/Commands.cs | 62 ----- .../Commands/ProcessAddFeedbackCommand.cs | 11 + .../Ticket/Commands/ProcessAddNoteCommand.cs | 9 + .../Commands/ProcessChangePriorityCommand.cs | 11 + .../ProcessChangeTicketTypeCommand.cs | 13 + .../Commands/ProcessCloseTicketCommand.cs | 11 + .../Commands/ProcessCreateNewTicketCommand.cs | 9 + .../ProcessCreateTicketTypeCommand.cs | 15 ++ .../Commands/ProcessModSendMessageCommand.cs | 12 + .../ProcessRemoveTicketTypeCommand.cs | 11 + .../Commands/ProcessToggleAnonymousCommand.cs | 9 + .../ProcessUpdateTicketTypeCommand.cs | 9 + .../Commands/ProcessUserSentMessageCommand.cs | 9 + .../Handlers/CheckTicketTypeExistsHandler.cs | 3 +- .../GetActiveTicketByUserIdHandler.cs | 8 +- .../Ticket/Handlers/GetTicketHandler.cs | 8 +- .../Handlers/GetTicketListByTypeHandler.cs | 5 +- .../GetTicketTypeByChannelIdHandler.cs | 11 +- .../Handlers/GetTicketTypeBySearchHandler.cs | 11 +- .../Handlers/GetTicketTypeHandler.cs | 10 +- .../Handlers/GetTicketTypeListHandler.cs | 8 +- .../Handlers/ProcessAddFeedbackHandler.cs | 9 +- .../Ticket/Handlers/ProcessAddNoteHandler.cs | 9 +- .../Handlers/ProcessChangePriorityHandler.cs | 24 +- .../ProcessChangeTicketTypeHandler.cs | 13 +- .../Handlers/ProcessCloseTicketHandler.cs | 16 +- .../Handlers/ProcessCreateNewTicketHandler.cs | 41 +-- .../ProcessCreateTicketTypeHandler.cs | 11 +- .../Handlers/ProcessModSendMessageHandler.cs | 18 +- .../ProcessRemoveTicketTypeHandler.cs | 12 +- .../Handlers/ProcessToggleAnonymousHandler.cs | 7 +- .../ProcessUpdateTicketTypeHandler.cs | 5 +- .../Handlers/ProcessUserSentMessageHandler.cs | 18 +- .../Ticket/Helpers/LogBotMessages.cs} | 25 +- .../Ticket/Helpers/TicketBotMessages.cs | 236 ++++++++++++++++++ .../Ticket/Helpers/TicketModals.cs} | 7 +- .../Ticket}/Jobs/TicketDataDeleteJob.cs | 6 +- .../Ticket}/Jobs/TicketTimeoutJob.cs | 14 +- .../Jobs/TicketTypeSelectionTimeoutJob.cs | 4 +- .../Ticket/Mappers/TicketDtoMapper.cs | 12 + .../Ticket/Models}/TicketDto.cs | 4 +- src/Modmail.NET/Features/Ticket/Queries.cs | 17 -- .../Queries/CheckTicketTypeExistsQuery.cs | 5 + .../Ticket/Queries/GetTicketByUserIdQuery.cs | 9 + .../Queries/GetTicketListByTypeQuery.cs | 5 + .../Features/Ticket/Queries/GetTicketQuery.cs | 9 + .../Queries/GetTicketTypeByChannelIdQuery.cs | 8 + .../Queries/GetTicketTypeBySearchQuery.cs | 8 + .../Ticket/Queries/GetTicketTypeListQuery.cs | 6 + .../Ticket/Queries/GetTicketTypeQuery.cs | 8 + .../TicketAttachmentDownloadService.cs | 12 +- .../Ticket/Services/TicketMessage.cs} | 53 ++-- .../Features/Ticket/Static/TicketConstants.cs | 17 ++ .../Ticket}/Static/TicketPriority.cs | 2 +- .../Features/TicketType/Commands.cs | 22 -- .../Features/TicketType/Queries.cs | 19 -- .../Commands/UpdateDiscordUserCommand.cs} | 4 +- .../Handlers/GetDiscordUserInfoDictHandler.cs | 5 +- .../Handlers/GetDiscordUserInfoHandler.cs | 9 +- .../Handlers/GetDiscordUserInfoListHandler.cs | 5 +- .../Handlers/UpdateDiscordUserHandler.cs | 7 +- .../User}/Jobs/DiscordUserInfoSyncJob.cs | 8 +- .../Queries/GetDiscordUserInfoDictQuery.cs | 8 + .../Queries/GetDiscordUserInfoListQuery.cs | 8 + .../User/Queries/GetDiscordUserInfoQuery.cs | 8 + src/Modmail.NET/Features/UserInfo/Queries.cs | 14 -- src/Modmail.NET/GlobalUsing.cs | 3 +- src/Modmail.NET/Language/LangProvider.cs | 2 +- .../Language/StringInteropProvider.cs | 2 +- src/Modmail.NET/Mappers/TicketDtoMapper.cs | 13 - src/Modmail.NET/Modmail.NET.csproj | 7 + src/Modmail.NET/ModmailBot.cs | 8 +- .../Pipeline/CachingPipelineBehavior.cs | 2 +- .../Pipeline/LoggerPipelineBehavior.cs | 4 +- .../PermissionCheckPipelineBehavior.cs | 3 +- src/Modmail.NET/Providers/TeamProvider.cs | 29 --- .../{Dependency.cs => ServiceLocator.cs} | 1 + src/Modmail.NET/Static/Const.cs | 25 -- src/Modmail.NET/Static/Interactions.cs | 22 -- src/Modmail.NET/Static/TicketResponses.cs | 109 -------- src/Modmail.NET/Static/UserResponses.cs | 158 ------------ src/Modmail.NET/Static/Webhooks.cs | 22 -- 385 files changed, 1901 insertions(+), 1502 deletions(-) rename src/{Modmail.NET/Extensions/ExtWeb.cs => Modmail.NET.Web.Blazor/Extensions/WebExtensions.cs} (82%) delete mode 100644 src/Modmail.NET/Abstract/BotExceptionBase.cs delete mode 100644 src/Modmail.NET/Abstract/IEntity.cs rename src/Modmail.NET/Abstract/{BaseQueue.cs => MemoryQueueBase.cs} (92%) rename src/Modmail.NET/{ => Common}/Aspects/PerformanceLoggerAspect.cs (96%) create mode 100644 src/Modmail.NET/Common/Exceptions/AnotherServerAlreadySetupException.cs rename src/Modmail.NET/{ => Common}/Exceptions/CanNotDeleteTicketTypeWhenActiveTicketsException.cs (69%) create mode 100644 src/Modmail.NET/Common/Exceptions/DbInternalException.cs rename src/Modmail.NET/{ => Common}/Exceptions/EmptyListResultException.cs (54%) create mode 100644 src/Modmail.NET/Common/Exceptions/InvalidInteractionKeyException.cs create mode 100644 src/Modmail.NET/Common/Exceptions/InvalidMessageIdException.cs rename src/Modmail.NET/{ => Common}/Exceptions/InvalidNameException.cs (56%) create mode 100644 src/Modmail.NET/Common/Exceptions/InvalidUserIdException.cs create mode 100644 src/Modmail.NET/Common/Exceptions/MainServerAlreadySetupException.cs create mode 100644 src/Modmail.NET/Common/Exceptions/MemberAlreadyInTeamException.cs create mode 100644 src/Modmail.NET/Common/Exceptions/ModmailBotException.cs rename src/Modmail.NET/{ => Common}/Exceptions/NotFoundException.cs (57%) rename src/Modmail.NET/{ => Common}/Exceptions/NotFoundInException.cs (67%) rename src/Modmail.NET/{ => Common}/Exceptions/NotFoundWithException.cs (64%) create mode 100644 src/Modmail.NET/Common/Exceptions/NotJoinedMainServerException.cs create mode 100644 src/Modmail.NET/Common/Exceptions/RoleAlreadyInTeamException.cs create mode 100644 src/Modmail.NET/Common/Exceptions/ServerIsNotSetupException.cs create mode 100644 src/Modmail.NET/Common/Exceptions/TeamAlreadyExistsException.cs create mode 100644 src/Modmail.NET/Common/Exceptions/TeamNotExistsException.cs create mode 100644 src/Modmail.NET/Common/Exceptions/TicketAlreadyClosedException.cs create mode 100644 src/Modmail.NET/Common/Exceptions/TicketMustBeClosedException.cs create mode 100644 src/Modmail.NET/Common/Exceptions/TicketTimeoutOutOfRangeException.cs rename src/Modmail.NET/{ => Common}/Exceptions/TicketTypeAlreadyExistsException.cs (57%) rename src/Modmail.NET/{ => Common}/Exceptions/TicketTypeNotExistsException.cs (58%) create mode 100644 src/Modmail.NET/Common/Exceptions/UserAlreadyBlacklistedException.cs create mode 100644 src/Modmail.NET/Common/Exceptions/UserIsNotBlacklistedException.cs rename src/Modmail.NET/{Extensions/ExtDiscordUser.cs => Common/Extensions/DiscordUserExtensions.cs} (87%) rename src/Modmail.NET/{Extensions/ExtEmbed.cs => Common/Extensions/EmbedExtensions.cs} (89%) create mode 100644 src/Modmail.NET/Common/Extensions/ExceptionExtensions.cs rename src/Modmail.NET/{Extensions/ExtString.cs => Common/Extensions/StringExtensions.cs} (68%) rename src/Modmail.NET/{ => Common}/Static/AuthPolicy.cs (96%) create mode 100644 src/Modmail.NET/Common/Static/Const.cs rename src/Modmail.NET/{ => Common}/Static/DbLength.cs (93%) rename src/Modmail.NET/{ => Common}/Static/DbType.cs (91%) rename src/Modmail.NET/{ => Common}/Static/EnvironmentType.cs (60%) rename src/Modmail.NET/{ => Common}/Static/HangfireQueueName.cs (90%) rename src/Modmail.NET/{Static/Colors.cs => Common/Static/ModmailColors.cs} (93%) rename src/Modmail.NET/{Static/Embeds.cs => Common/Static/ModmailEmbeds.cs} (74%) create mode 100644 src/Modmail.NET/Common/Static/ModmailInteractions.cs create mode 100644 src/Modmail.NET/Common/Static/ModmailWebhooks.cs rename src/Modmail.NET/{ => Common}/Utils/UtilAttachment.cs (56%) rename src/Modmail.NET/{ => Common}/Utils/UtilCache.cs (96%) rename src/Modmail.NET/{ => Common}/Utils/UtilChannelTopic.cs (94%) rename src/Modmail.NET/{ => Common}/Utils/UtilDate.cs (72%) rename src/Modmail.NET/{ => Common}/Utils/UtilHash.cs (95%) rename src/Modmail.NET/{ => Common}/Utils/UtilInteraction.cs (87%) rename src/Modmail.NET/{ => Common}/Utils/UtilMention.cs (77%) rename src/Modmail.NET/{ => Common}/Utils/UtilPermission.cs (96%) rename src/Modmail.NET/{ => Common}/Utils/UtilReadable.cs (98%) rename src/Modmail.NET/{ => Common}/Utils/UtilVersion.cs (95%) create mode 100644 src/Modmail.NET/Database/Abstract/IEntity.cs rename src/Modmail.NET/{ => Database}/Abstract/IGuidId.cs (59%) rename src/Modmail.NET/{ => Database}/Abstract/IHasRegisterDate.cs (65%) rename src/Modmail.NET/{ => Database}/Abstract/IHasUpdateDate.cs (64%) rename src/Modmail.NET/{ => Database}/Entities/DiscordUserInfo.cs (91%) rename src/Modmail.NET/{ => Database}/Entities/GuildOption.cs (73%) rename src/Modmail.NET/{ => Database}/Entities/GuildTeam.cs (82%) rename src/Modmail.NET/{ => Database}/Entities/GuildTeamMember.cs (76%) rename src/Modmail.NET/{ => Database}/Entities/Statistic.cs (92%) rename src/Modmail.NET/{ => Database}/Entities/Ticket.cs (89%) rename src/Modmail.NET/{ => Database}/Entities/TicketBlacklist.cs (79%) rename src/Modmail.NET/{ => Database}/Entities/TicketMessage.cs (90%) rename src/Modmail.NET/{ => Database}/Entities/TicketMessageAttachment.cs (91%) rename src/Modmail.NET/{ => Database}/Entities/TicketNote.cs (79%) rename src/Modmail.NET/{ => Database}/Entities/TicketType.cs (89%) rename src/Modmail.NET/{ => Database}/Migrations/20240827120247_Initial.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240827120247_Initial.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240827121750_Ticket_AssignedUser.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240827121750_Ticket_AssignedUser.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240827153553_GuildOption_Removed_GreetingAndClosingMessage.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240827153553_GuildOption_Removed_GreetingAndClosingMessage.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240828104105_RemovedUnusedFK.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240828104105_RemovedUnusedFK.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240830090726_GuildTeamMember_Removed_UpdateDateUtc.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240830090726_GuildTeamMember_Removed_UpdateDateUtc.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240901191906_TicketType_Added_InEnabled.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240901191906_TicketType_Added_InEnabled.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240907082958_TicketType_Updated_EmbedIsNullable.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240907082958_TicketType_Updated_EmbedIsNullable.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240907090419_TicketType_Update_DescriptionIsRequired.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240907090419_TicketType_Update_DescriptionIsRequired.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240907091824_GuildOption_AddedCol_IsEnableDiscordChannelLogging.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20240907091824_GuildOption_AddedCol_IsEnableDiscordChannelLogging.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250317224506_GuildOption_AddedColumn_AlwaysAnonymous.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250317224506_GuildOption_AddedColumn_AlwaysAnonymous.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250317230425_GuildOption_ColumnAdded_SlashCommandDisableColumns.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250317230425_GuildOption_ColumnAdded_SlashCommandDisableColumns.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250319000242_GuildOption_ColAdded_AllowUsersToCloseTickets.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250319000242_GuildOption_ColAdded_AllowUsersToCloseTickets.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250319013409_GuildOption_AddedAvarageResponseTime.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250319013409_GuildOption_AddedAvarageResponseTime.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250319030317_GuildOption_AddedCols_AvgData.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250319030317_GuildOption_AddedCols_AvgData.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250321163931_TicketMessageAttachment_ByteContent_Removed.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250321163931_TicketMessageAttachment_ByteContent_Removed.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250321164005_GuildOption_IconUrl_Nullable_BugFix.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250321164005_GuildOption_IconUrl_Nullable_BugFix.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250322125230_GuildOption_RemovedSomeColumns.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250322125230_GuildOption_RemovedSomeColumns.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250322184829_GuildOption_AddedCol_PermissionLevel.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250322184829_GuildOption_AddedCol_PermissionLevel.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250322225844_TicketMessageSentByModColAdded.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250322225844_TicketMessageSentByModColAdded.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250323013715_StatisticTableAdded.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250323013715_StatisticTableAdded.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250323173554_GuildTeamAddedColAllowAccessToWebPanel.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250323173554_GuildTeamAddedColAllowAccessToWebPanel.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250323192315_GuildOption_RemovedFeatureOptions.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250323192315_GuildOption_RemovedFeatureOptions.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250323192910_GuildOptionRemoveOptionPermAccessLevel.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250323192910_GuildOptionRemoveOptionPermAccessLevel.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250326132054_StatisticUpdatedColNames.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250326132054_StatisticUpdatedColNames.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250326235413_StatisticRenameResolvedTickets.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250326235413_StatisticRenameResolvedTickets.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250329182313_GuildOptionRemovedShowConfirmationOnCloseCol.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250329182313_GuildOptionRemovedShowConfirmationOnCloseCol.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250329204247_GuildOptionPublicTranscripts.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250329204247_GuildOptionPublicTranscripts.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250329213018_TicketMessageLengthExtendedToNoLimit.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250329213018_TicketMessageLengthExtendedToNoLimit.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250329223813_GuildOptionSendTranscriptLinkToUserColAdded.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250329223813_GuildOptionSendTranscriptLinkToUserColAdded.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250406154808_TicketMessageAddedColumnBotMessageId.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250406154808_TicketMessageAddedColumnBotMessageId.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250406163216_GuildOptionAddedColTicketAutoDeleteWaitDays.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250406163216_GuildOptionAddedColTicketAutoDeleteWaitDays.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250406180016_Constraints.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250406180016_Constraints.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.cs (88%) rename src/Modmail.NET/{ => Database}/Migrations/20250406181350_GuildOptionConstraintFixMinValues.Designer.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/20250406181350_GuildOptionConstraintFixMinValues.cs (100%) rename src/Modmail.NET/{ => Database}/Migrations/ModmailDbContextModelSnapshot.cs (100%) delete mode 100644 src/Modmail.NET/Exceptions/AnotherServerAlreadySetupException.cs delete mode 100644 src/Modmail.NET/Exceptions/DbInternalException.cs delete mode 100644 src/Modmail.NET/Exceptions/InvalidInteractionKeyException.cs delete mode 100644 src/Modmail.NET/Exceptions/InvalidMessageIdException.cs delete mode 100644 src/Modmail.NET/Exceptions/InvalidUserIdException.cs delete mode 100644 src/Modmail.NET/Exceptions/MainServerAlreadySetupException.cs delete mode 100644 src/Modmail.NET/Exceptions/MemberAlreadyInTeamException.cs delete mode 100644 src/Modmail.NET/Exceptions/NotJoinedMainServerException.cs delete mode 100644 src/Modmail.NET/Exceptions/RoleAlreadyInTeamException.cs delete mode 100644 src/Modmail.NET/Exceptions/ServerIsNotSetupException.cs delete mode 100644 src/Modmail.NET/Exceptions/TeamAlreadyExistsException.cs delete mode 100644 src/Modmail.NET/Exceptions/TeamNotExistsException.cs delete mode 100644 src/Modmail.NET/Exceptions/TicketAlreadyClosedException.cs delete mode 100644 src/Modmail.NET/Exceptions/TicketMustBeClosedException.cs delete mode 100644 src/Modmail.NET/Exceptions/TicketTimeoutOutOfRangeException.cs delete mode 100644 src/Modmail.NET/Exceptions/UserAlreadyBlacklistedException.cs delete mode 100644 src/Modmail.NET/Exceptions/UserIsNotBlacklistedException.cs delete mode 100644 src/Modmail.NET/Extensions/ExtException.cs rename src/Modmail.NET/Features/Blacklist/{Commands.cs => Commands/ProcessAddUserToBlacklistCommand.cs} (51%) create mode 100644 src/Modmail.NET/Features/Blacklist/Commands/ProcessRemoveUserFromBlacklistCommand.cs delete mode 100644 src/Modmail.NET/Features/Blacklist/Queries.cs create mode 100644 src/Modmail.NET/Features/Blacklist/Queries/CheckUserBlacklistStatusQuery.cs create mode 100644 src/Modmail.NET/Features/Blacklist/Queries/GetAllBlacklistQuery.cs create mode 100644 src/Modmail.NET/Features/Blacklist/Queries/GetBlacklistQuery.cs create mode 100644 src/Modmail.NET/Features/Blacklist/Static/BlacklistBotMessages.cs delete mode 100644 src/Modmail.NET/Features/Bot/Queries.cs rename src/Modmail.NET/{ => Features/DiscordBot}/Events/ComponentInteractionCreatedEvent.cs (91%) rename src/Modmail.NET/{ => Features/DiscordBot}/Events/ModalSubmittedEvent.cs (91%) rename src/Modmail.NET/{ => Features/DiscordBot}/Events/OnChannelDeletedEvent.cs (88%) rename src/Modmail.NET/{ => Features/DiscordBot}/Events/OnMessageCreatedEvent.cs (82%) rename src/Modmail.NET/{ => Features/DiscordBot}/Events/OnMessageDeletedEvent.cs (98%) rename src/Modmail.NET/{ => Features/DiscordBot}/Events/OnMessageReactionAddedEvent.cs (96%) rename src/Modmail.NET/{ => Features/DiscordBot}/Events/OnMessageReactionRemovedEvent.cs (96%) rename src/Modmail.NET/{ => Features/DiscordBot}/Events/OnMessageUpdatedEvent.cs (97%) rename src/Modmail.NET/{ => Features/DiscordBot}/Events/UserUpdateEvents.cs (94%) rename src/Modmail.NET/Features/{Bot => DiscordBot}/Handlers/GetDiscordLogChannelHandler.cs (86%) rename src/Modmail.NET/Features/{Bot => DiscordBot}/Handlers/GetDiscordMainGuildHandler.cs (85%) rename src/Modmail.NET/Features/{Bot => DiscordBot}/Handlers/GetDiscordMemberHandler.cs (86%) create mode 100644 src/Modmail.NET/Features/DiscordBot/Queries/GetDiscordLogChannelQuery.cs create mode 100644 src/Modmail.NET/Features/DiscordBot/Queries/GetDiscordMainGuildQuery.cs create mode 100644 src/Modmail.NET/Features/DiscordBot/Queries/GetDiscordMemberQuery.cs rename src/Modmail.NET/{ => Features/DiscordCommands}/Checks/Attributes/RequireMainServerAttribute.cs (62%) rename src/Modmail.NET/{ => Features/DiscordCommands}/Checks/Attributes/RequirePermissionLevelOrHigherAttribute.cs (74%) rename src/Modmail.NET/{ => Features/DiscordCommands}/Checks/Attributes/RequireTicketChannelAttribute.cs (63%) rename src/Modmail.NET/{ => Features/DiscordCommands}/Checks/Attributes/UpdateUserInformationAttribute.cs (63%) rename src/Modmail.NET/{ => Features/DiscordCommands}/Checks/RequireMainServerCheck.cs (81%) rename src/Modmail.NET/{ => Features/DiscordCommands}/Checks/RequirePermissionLevelOrHigherCheck.cs (89%) rename src/Modmail.NET/{ => Features/DiscordCommands}/Checks/RequireTicketChannelCheck.cs (74%) rename src/Modmail.NET/{ => Features/DiscordCommands}/Checks/UpdateUserInformationCheck.cs (77%) rename src/Modmail.NET/{Commands/Slash => Features/DiscordCommands/Handlers}/BlacklistSlashCommands.cs (78%) rename src/Modmail.NET/{Commands => Features/DiscordCommands/Handlers}/ModmailCommands.cs (71%) rename src/Modmail.NET/{Commands/Slash => Features/DiscordCommands/Handlers}/TicketSlashCommands.cs (79%) rename src/Modmail.NET/{Providers => Features/DiscordCommands/Helpers}/TicketTypeProvider.cs (92%) delete mode 100644 src/Modmail.NET/Features/Guild/Commands.cs create mode 100644 src/Modmail.NET/Features/Guild/Commands/ClearGuildOptionCommand.cs create mode 100644 src/Modmail.NET/Features/Guild/Commands/ProcessCreateLogChannelCommand.cs create mode 100644 src/Modmail.NET/Features/Guild/Commands/ProcessGuildSetupCommand.cs create mode 100644 src/Modmail.NET/Features/Guild/Queries/CheckAnyGuildSetupQuery.cs rename src/Modmail.NET/Features/Guild/{Queries.cs => Queries/GetGuildOptionQuery.cs} (50%) create mode 100644 src/Modmail.NET/Features/Guild/Static/GuildConstants.cs delete mode 100644 src/Modmail.NET/Features/Metric/Commands.cs rename src/Modmail.NET/{ => Features/Metric}/Jobs/StatisticsCalculatorJob.cs (94%) rename src/Modmail.NET/{Models/Dto => Features/Metric/Models}/ChartItemDto.cs (65%) rename src/Modmail.NET/{Models/Dto => Features/Metric/Models}/MetricDto.cs (92%) rename src/Modmail.NET/Features/Metric/{Queries.cs => Queries/GetLatestMetricQuery.cs} (67%) create mode 100644 src/Modmail.NET/Features/Metric/Static/MetricConstants.cs rename src/Modmail.NET/{ => Features/Permission}/Models/PermissionInfo.cs (73%) delete mode 100644 src/Modmail.NET/Features/Permission/Queries.cs create mode 100644 src/Modmail.NET/Features/Permission/Queries/CheckPermissionAccessQuery.cs create mode 100644 src/Modmail.NET/Features/Permission/Queries/CheckRoleInAnyTeamQuery.cs create mode 100644 src/Modmail.NET/Features/Permission/Queries/CheckUserInAnyTeamQuery.cs create mode 100644 src/Modmail.NET/Features/Permission/Queries/GetPermissionInfoOrHigherQuery.cs create mode 100644 src/Modmail.NET/Features/Permission/Queries/GetPermissionInfoQuery.cs create mode 100644 src/Modmail.NET/Features/Permission/Queries/GetPermissionLevelQuery.cs rename src/Modmail.NET/{ => Features/Permission}/Static/TeamPermissionLevel.cs (64%) delete mode 100644 src/Modmail.NET/Features/Teams/Commands.cs create mode 100644 src/Modmail.NET/Features/Teams/Commands/ProcessAddRoleToTeamCommand.cs create mode 100644 src/Modmail.NET/Features/Teams/Commands/ProcessAddTeamMemberCommand.cs create mode 100644 src/Modmail.NET/Features/Teams/Commands/ProcessCreateTeamCommand.cs create mode 100644 src/Modmail.NET/Features/Teams/Commands/ProcessRemoveTeamCommand.cs create mode 100644 src/Modmail.NET/Features/Teams/Commands/ProcessRemoveTeamMemberCommand.cs create mode 100644 src/Modmail.NET/Features/Teams/Commands/ProcessRenameTeamCommand.cs create mode 100644 src/Modmail.NET/Features/Teams/Commands/ProcessUpdateTeamCommand.cs delete mode 100644 src/Modmail.NET/Features/Teams/Queries.cs create mode 100644 src/Modmail.NET/Features/Teams/Queries/CheckTeamExistsQuery.cs create mode 100644 src/Modmail.NET/Features/Teams/Queries/GetTeamByNameQuery.cs create mode 100644 src/Modmail.NET/Features/Teams/Queries/GetTeamListQuery.cs create mode 100644 src/Modmail.NET/Features/Teams/Queries/GetTeamQuery.cs rename src/Modmail.NET/{ => Features/Teams}/Static/TeamMemberDataType.cs (80%) delete mode 100644 src/Modmail.NET/Features/Ticket/Commands.cs create mode 100644 src/Modmail.NET/Features/Ticket/Commands/ProcessAddFeedbackCommand.cs create mode 100644 src/Modmail.NET/Features/Ticket/Commands/ProcessAddNoteCommand.cs create mode 100644 src/Modmail.NET/Features/Ticket/Commands/ProcessChangePriorityCommand.cs create mode 100644 src/Modmail.NET/Features/Ticket/Commands/ProcessChangeTicketTypeCommand.cs create mode 100644 src/Modmail.NET/Features/Ticket/Commands/ProcessCloseTicketCommand.cs create mode 100644 src/Modmail.NET/Features/Ticket/Commands/ProcessCreateNewTicketCommand.cs create mode 100644 src/Modmail.NET/Features/Ticket/Commands/ProcessCreateTicketTypeCommand.cs create mode 100644 src/Modmail.NET/Features/Ticket/Commands/ProcessModSendMessageCommand.cs create mode 100644 src/Modmail.NET/Features/Ticket/Commands/ProcessRemoveTicketTypeCommand.cs create mode 100644 src/Modmail.NET/Features/Ticket/Commands/ProcessToggleAnonymousCommand.cs create mode 100644 src/Modmail.NET/Features/Ticket/Commands/ProcessUpdateTicketTypeCommand.cs create mode 100644 src/Modmail.NET/Features/Ticket/Commands/ProcessUserSentMessageCommand.cs rename src/Modmail.NET/Features/{TicketType => Ticket}/Handlers/CheckTicketTypeExistsHandler.cs (86%) rename src/Modmail.NET/Features/{TicketType => Ticket}/Handlers/GetTicketTypeByChannelIdHandler.cs (64%) rename src/Modmail.NET/Features/{TicketType => Ticket}/Handlers/GetTicketTypeBySearchHandler.cs (63%) rename src/Modmail.NET/Features/{TicketType => Ticket}/Handlers/GetTicketTypeHandler.cs (60%) rename src/Modmail.NET/Features/{TicketType => Ticket}/Handlers/GetTicketTypeListHandler.cs (63%) rename src/Modmail.NET/Features/{TicketType => Ticket}/Handlers/ProcessCreateTicketTypeHandler.cs (83%) rename src/Modmail.NET/Features/{TicketType => Ticket}/Handlers/ProcessRemoveTicketTypeHandler.cs (73%) rename src/Modmail.NET/Features/{TicketType => Ticket}/Handlers/ProcessUpdateTicketTypeHandler.cs (89%) rename src/Modmail.NET/{Static/LogResponses.cs => Features/Ticket/Helpers/LogBotMessages.cs} (80%) create mode 100644 src/Modmail.NET/Features/Ticket/Helpers/TicketBotMessages.cs rename src/Modmail.NET/{Static/Modals.cs => Features/Ticket/Helpers/TicketModals.cs} (92%) rename src/Modmail.NET/{ => Features/Ticket}/Jobs/TicketDataDeleteJob.cs (95%) rename src/Modmail.NET/{ => Features/Ticket}/Jobs/TicketTimeoutJob.cs (86%) rename src/Modmail.NET/{ => Features/Ticket}/Jobs/TicketTypeSelectionTimeoutJob.cs (94%) create mode 100644 src/Modmail.NET/Features/Ticket/Mappers/TicketDtoMapper.cs rename src/Modmail.NET/{Models/Dto => Features/Ticket/Models}/TicketDto.cs (87%) delete mode 100644 src/Modmail.NET/Features/Ticket/Queries.cs create mode 100644 src/Modmail.NET/Features/Ticket/Queries/CheckTicketTypeExistsQuery.cs create mode 100644 src/Modmail.NET/Features/Ticket/Queries/GetTicketByUserIdQuery.cs create mode 100644 src/Modmail.NET/Features/Ticket/Queries/GetTicketListByTypeQuery.cs create mode 100644 src/Modmail.NET/Features/Ticket/Queries/GetTicketQuery.cs create mode 100644 src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeByChannelIdQuery.cs create mode 100644 src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeBySearchQuery.cs create mode 100644 src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeListQuery.cs create mode 100644 src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeQuery.cs rename src/Modmail.NET/{ => Features/Ticket}/Services/TicketAttachmentDownloadService.cs (73%) rename src/Modmail.NET/{Queues/TicketMessageQueue.cs => Features/Ticket/Services/TicketMessage.cs} (77%) create mode 100644 src/Modmail.NET/Features/Ticket/Static/TicketConstants.cs rename src/Modmail.NET/{ => Features/Ticket}/Static/TicketPriority.cs (82%) delete mode 100644 src/Modmail.NET/Features/TicketType/Commands.cs delete mode 100644 src/Modmail.NET/Features/TicketType/Queries.cs rename src/Modmail.NET/Features/{UserInfo/Commands.cs => User/Commands/UpdateDiscordUserCommand.cs} (63%) rename src/Modmail.NET/Features/{UserInfo => User}/Handlers/GetDiscordUserInfoDictHandler.cs (86%) rename src/Modmail.NET/Features/{UserInfo => User}/Handlers/GetDiscordUserInfoHandler.cs (85%) rename src/Modmail.NET/Features/{UserInfo => User}/Handlers/GetDiscordUserInfoListHandler.cs (81%) rename src/Modmail.NET/Features/{UserInfo => User}/Handlers/UpdateDiscordUserHandler.cs (89%) rename src/Modmail.NET/{ => Features/User}/Jobs/DiscordUserInfoSyncJob.cs (92%) create mode 100644 src/Modmail.NET/Features/User/Queries/GetDiscordUserInfoDictQuery.cs create mode 100644 src/Modmail.NET/Features/User/Queries/GetDiscordUserInfoListQuery.cs create mode 100644 src/Modmail.NET/Features/User/Queries/GetDiscordUserInfoQuery.cs delete mode 100644 src/Modmail.NET/Features/UserInfo/Queries.cs delete mode 100644 src/Modmail.NET/Mappers/TicketDtoMapper.cs delete mode 100644 src/Modmail.NET/Providers/TeamProvider.cs rename src/Modmail.NET/{Dependency.cs => ServiceLocator.cs} (98%) delete mode 100644 src/Modmail.NET/Static/Const.cs delete mode 100644 src/Modmail.NET/Static/Interactions.cs delete mode 100644 src/Modmail.NET/Static/TicketResponses.cs delete mode 100644 src/Modmail.NET/Static/UserResponses.cs delete mode 100644 src/Modmail.NET/Static/Webhooks.cs diff --git a/src/Modmail.NET.Web.Blazor/Components/App.razor b/src/Modmail.NET.Web.Blazor/Components/App.razor index 5d197af0..99d10b44 100644 --- a/src/Modmail.NET.Web.Blazor/Components/App.razor +++ b/src/Modmail.NET.Web.Blazor/Components/App.razor @@ -1,4 +1,4 @@ -@using Modmail.NET.Static +@using Modmail.NET.Common.Static @using Modmail.NET.Web.Blazor.Components.Layout.Shared @inject ThemeService ThemeService diff --git a/src/Modmail.NET.Web.Blazor/Components/Layout/AuthLayout.razor b/src/Modmail.NET.Web.Blazor/Components/Layout/AuthLayout.razor index d44e972c..03583603 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Layout/AuthLayout.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Layout/AuthLayout.razor @@ -1,7 +1,7 @@ -@using Modmail.NET.Extensions -@using Modmail.NET.Features.Permission -@using Modmail.NET.Static +@using Modmail.NET.Common.Static +@using Modmail.NET.Features.Permission.Queries @using Modmail.NET.Web.Blazor.Components.Layout.Shared +@using Modmail.NET.Web.Blazor.Extensions @using Modmail.NET.Web.Blazor.Providers @inherits LayoutComponentBase @inject NavigationManager NavigationManager diff --git a/src/Modmail.NET.Web.Blazor/Components/Layout/Shared/AppFooter.razor b/src/Modmail.NET.Web.Blazor/Components/Layout/Shared/AppFooter.razor index 6eb8ca20..9c5c158a 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Layout/Shared/AppFooter.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Layout/Shared/AppFooter.razor @@ -1,4 +1,4 @@ -@using Modmail.NET.Utils +@using Modmail.NET.Common.Utils diff --git a/src/Modmail.NET.Web.Blazor/Components/Pages/Blacklist.razor b/src/Modmail.NET.Web.Blazor/Components/Pages/Blacklist.razor index 006fee7c..7482a28a 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Pages/Blacklist.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Pages/Blacklist.razor @@ -1,10 +1,11 @@ @page "/blacklist" @using Microsoft.EntityFrameworkCore -@using Modmail.NET.Abstract -@using Modmail.NET.Extensions -@using Modmail.NET.Features.Blacklist -@using Modmail.NET.Static +@using Modmail.NET.Common.Exceptions +@using Modmail.NET.Common.Static +@using Modmail.NET.Database.Entities +@using Modmail.NET.Features.Blacklist.Commands @using Modmail.NET.Web.Blazor.Components.Shared.Blacklist +@using Modmail.NET.Web.Blazor.Extensions @using Modmail.NET.Web.Blazor.Providers @using Serilog @inject IDbContextFactory DbContextFactory @@ -91,7 +92,7 @@ "User removed from blacklist"); await ReloadDataAsync(); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning(ex, logMessage, blacklist.DiscordUserId); diff --git a/src/Modmail.NET.Web.Blazor/Components/Pages/Dashboard.razor b/src/Modmail.NET.Web.Blazor/Components/Pages/Dashboard.razor index 210c65fc..34cb3fba 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Pages/Dashboard.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Pages/Dashboard.razor @@ -1,17 +1,18 @@ @page "/dashboard" -@using Modmail.NET.Extensions -@using Modmail.NET.Features.Guild -@using Modmail.NET.Features.Metric -@using Modmail.NET.Features.Permission -@using Modmail.NET.Queues -@using Modmail.NET.Static -@using Modmail.NET.Utils +@using Modmail.NET.Common.Static +@using Modmail.NET.Common.Utils +@using Modmail.NET.Features.Guild.Queries +@using Modmail.NET.Features.Metric.Models +@using Modmail.NET.Features.Metric.Queries +@using Modmail.NET.Features.Permission.Queries +@using Modmail.NET.Features.Ticket.Services +@using Modmail.NET.Web.Blazor.Extensions @using Modmail.NET.Web.Blazor.Providers @inject NavigationManager NavigationManager @inject DialogService DialogService @inject NotificationService NotificationService @inject ModmailBot Bot -@inject TicketMessageQueue TicketMessageQueue +@inject TicketMessage TicketMessage @inject IServiceScopeFactory ScopeFactory @attribute [AuthorizeTeam] @@ -235,7 +236,7 @@ - @(UtilReadable.ConvertNumberToReadableString(TicketMessageQueue.GetChannelCount())) + @(UtilReadable.ConvertNumberToReadableString(TicketMessage.GetChannelCount())) Processing Message Queues diff --git a/src/Modmail.NET.Web.Blazor/Components/Pages/Options.razor b/src/Modmail.NET.Web.Blazor/Components/Pages/Options.razor index 0c7ddf32..2bfd09a4 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Pages/Options.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Pages/Options.razor @@ -1,8 +1,12 @@ @page "/options" @using Microsoft.EntityFrameworkCore @using Microsoft.Extensions.Options -@using Modmail.NET.Abstract -@using Modmail.NET.Static +@using Modmail.NET.Common.Exceptions +@using Modmail.NET.Common.Static +@using Modmail.NET.Database.Entities +@using Modmail.NET.Features.Metric.Static +@using Modmail.NET.Features.Permission.Static +@using Modmail.NET.Features.Ticket.Static @using Modmail.NET.Web.Blazor.Providers @using Serilog @inject NavigationManager NavigationManager @@ -46,12 +50,12 @@ throw new InvalidOperationException("Guild option is null"); } - if (_guildOption.TicketTimeoutHours is > Const.TicketTimeoutMaxAllowedHours or < Const.TicketTimeoutMinAllowedHours && _enableTicketTimeout) { + if (_guildOption.TicketTimeoutHours is > TicketConstants.TicketTimeoutMaxAllowedHours or < TicketConstants.TicketTimeoutMinAllowedHours && _enableTicketTimeout) { NotificationService.Notify(NotificationSeverity.Warning, "Warning", "Ticket Timeout Hours is not in valid range."); return; } - if (_guildOption.TicketDataDeleteWaitDays is > Const.TicketDataDeleteWaitDaysMax or < Const.TicketDataDeleteWaitDaysMin && _enableTicketAutoDelete) { + if (_guildOption.TicketDataDeleteWaitDays is > TicketConstants.TicketDataDeleteWaitDaysMax or < TicketConstants.TicketDataDeleteWaitDaysMin && _enableTicketAutoDelete) { NotificationService.Notify(NotificationSeverity.Warning, "Warning", "Auto Delete Ticket Data Wait Days is not in valid range."); return; } @@ -78,7 +82,7 @@ NotificationService.Notify(NotificationSeverity.Success, "Success", "Guild options updated successfully."); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { ex.NotifyException(NotificationService); Log.Warning(ex, logMessage); } @@ -166,12 +170,14 @@ + Min="TicketConstants.TicketTimeoutMinAllowedHours" + Max="TicketConstants.TicketTimeoutMaxAllowedHours"/> - Set the number of hours (@Const.TicketTimeoutMinAllowedHours~@Const.TicketTimeoutMaxAllowedHours) a + Set the number of hours + (@TicketConstants.TicketTimeoutMinAllowedHours~@TicketConstants.TicketTimeoutMaxAllowedHours) a ticket can be inactive before it's automatically closed. @@ -188,12 +194,14 @@ + Min="TicketConstants.TicketDataDeleteWaitDaysMin" + Max="TicketConstants.TicketDataDeleteWaitDaysMax"/> - Set the number of days (@Const.TicketDataDeleteWaitDaysMin~@Const.TicketDataDeleteWaitDaysMax) after + Set the number of days + (@TicketConstants.TicketDataDeleteWaitDaysMin~@TicketConstants.TicketDataDeleteWaitDaysMax) after which ticket data is deleted. @@ -202,13 +210,14 @@ + Min="MetricConstants.StatisticsCalculateDaysMin" + Max="MetricConstants.StatisticsCalculateDaysMax"/> Days to include in statistics calculation - (@Const.StatisticsCalculateDaysMin~@Const.StatisticsCalculateDaysMax). + (@MetricConstants.StatisticsCalculateDaysMin~@MetricConstants.StatisticsCalculateDaysMax). diff --git a/src/Modmail.NET.Web.Blazor/Components/Pages/Setup.razor b/src/Modmail.NET.Web.Blazor/Components/Pages/Setup.razor index 6e9992f4..ca89b9fb 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Pages/Setup.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Pages/Setup.razor @@ -1,6 +1,6 @@ @page "/setup" -@using Modmail.NET.Features.Guild -@using Modmail.NET.Static +@using Modmail.NET.Common.Static +@using Modmail.NET.Features.Guild.Queries @using Modmail.NET.Web.Blazor.Components.Layout @using Modmail.NET.Web.Blazor.Providers @inject NavigationManager NavigationManager diff --git a/src/Modmail.NET.Web.Blazor/Components/Pages/Teams.razor b/src/Modmail.NET.Web.Blazor/Components/Pages/Teams.razor index 58afce87..fe95d9a1 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Pages/Teams.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Pages/Teams.razor @@ -1,10 +1,11 @@ @page "/teams" @using Microsoft.EntityFrameworkCore -@using Modmail.NET.Abstract -@using Modmail.NET.Extensions -@using Modmail.NET.Features.Teams -@using Modmail.NET.Static +@using Modmail.NET.Common.Exceptions +@using Modmail.NET.Common.Static +@using Modmail.NET.Database.Entities +@using Modmail.NET.Features.Teams.Commands @using Modmail.NET.Web.Blazor.Components.Shared.Teams +@using Modmail.NET.Web.Blazor.Extensions @using Modmail.NET.Web.Blazor.Providers @using Serilog @inject IDbContextFactory DbContextFactory @@ -224,7 +225,7 @@ await ReloadDataAsync(); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning(ex, logMessage, team.Name); diff --git a/src/Modmail.NET.Web.Blazor/Components/Pages/TicketTypes.razor b/src/Modmail.NET.Web.Blazor/Components/Pages/TicketTypes.razor index 08d30ba2..62b70c27 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Pages/TicketTypes.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Pages/TicketTypes.razor @@ -1,10 +1,11 @@ @page "/ticket-types" @using Microsoft.EntityFrameworkCore -@using Modmail.NET.Abstract -@using Modmail.NET.Extensions -@using Modmail.NET.Features.TicketType -@using Modmail.NET.Static +@using Modmail.NET.Common.Exceptions +@using Modmail.NET.Common.Static +@using Modmail.NET.Database.Entities +@using Modmail.NET.Features.Ticket.Commands @using Modmail.NET.Web.Blazor.Components.Shared.TicketType +@using Modmail.NET.Web.Blazor.Extensions @using Modmail.NET.Web.Blazor.Providers @using Serilog @inject IDbContextFactory DbContextFactory @@ -94,7 +95,7 @@ await ReloadDataAsync(); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning(ex, logMessage, ticketType); diff --git a/src/Modmail.NET.Web.Blazor/Components/Pages/Tickets.razor b/src/Modmail.NET.Web.Blazor/Components/Pages/Tickets.razor index be739e48..814e5cec 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Pages/Tickets.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Pages/Tickets.razor @@ -1,12 +1,14 @@ @page "/tickets" @using Microsoft.EntityFrameworkCore -@using Modmail.NET.Abstract -@using Modmail.NET.Features.Ticket -@using Modmail.NET.Static +@using Modmail.NET.Common.Exceptions +@using Modmail.NET.Common.Static +@using Modmail.NET.Features.Ticket.Commands +@using Modmail.NET.Features.Ticket.Models @using Modmail.NET.Web.Blazor.Components.Shared.Tickets @using Modmail.NET.Web.Blazor.Providers @using Modmail.NET.Web.Blazor.Static @using Serilog +@using TicketDtoMapper = Modmail.NET.Features.Ticket.Mappers.TicketDtoMapper @inject IDbContextFactory DbContextFactory @inject TooltipService TooltipService @inject DialogService DialogService @@ -34,14 +36,13 @@ await ShowLoading(); var dbContext = await DbContextFactory.CreateDbContextAsync(); - var query = dbContext.Tickets - .AsNoTracking() - .Include(x => x.OpenerUser) - .Include(x => x.CloserUser) - .Include(x => x.TicketType) - .OrderByDescending(x => x.RegisterDateUtc) - .ProjectToDto() - .AsQueryable(); + var query = TicketDtoMapper.ProjectToDto(dbContext.Tickets + .AsNoTracking() + .Include(x => x.OpenerUser) + .Include(x => x.CloserUser) + .Include(x => x.TicketType) + .OrderByDescending(x => x.RegisterDateUtc)) + .AsQueryable(); switch (_filterTicketType) { case TicketFilterType.All: @@ -116,7 +117,7 @@ NotificationService.Notify(NotificationSeverity.Success, "Success", "The ticket has been closed successfully."); await ReloadDataAsync(); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { ex.NotifyException(NotificationService); Log.Warning(ex, logMessage, _forceCloseReason); } diff --git a/src/Modmail.NET.Web.Blazor/Components/Pages/Transcript.razor b/src/Modmail.NET.Web.Blazor/Components/Pages/Transcript.razor index 52cab7e8..434616b2 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Pages/Transcript.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Pages/Transcript.razor @@ -1,8 +1,9 @@ @page "/transcript/{TicketId:guid}" @using CSharpVitamins @using Microsoft.EntityFrameworkCore -@using Modmail.NET.Features.Guild -@using Modmail.NET.Features.UserInfo +@using Modmail.NET.Database.Entities +@using Modmail.NET.Features.Guild.Queries +@using Modmail.NET.Features.User.Queries @using Modmail.NET.Web.Blazor.Components.Layout @using Path = Path @layout EmptyLayout diff --git a/src/Modmail.NET.Web.Blazor/Components/Shared/AccountDialog.razor b/src/Modmail.NET.Web.Blazor/Components/Shared/AccountDialog.razor index 96c97533..bb8ec020 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Shared/AccountDialog.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Shared/AccountDialog.razor @@ -1,6 +1,6 @@ @using System.Security.Claims +@using Modmail.NET.Features.Permission.Static @using Modmail.NET.Language -@using Modmail.NET.Static @inject NavigationManager NavigationManager @code { diff --git a/src/Modmail.NET.Web.Blazor/Components/Shared/Blacklist/AddBlacklistDialog.razor b/src/Modmail.NET.Web.Blazor/Components/Shared/Blacklist/AddBlacklistDialog.razor index bf2ffaa2..23e302b6 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Shared/Blacklist/AddBlacklistDialog.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Shared/Blacklist/AddBlacklistDialog.razor @@ -1,8 +1,9 @@ @using Microsoft.EntityFrameworkCore @using Microsoft.Extensions.Options -@using Modmail.NET.Abstract -@using Modmail.NET.Extensions -@using Modmail.NET.Features.Blacklist +@using Modmail.NET.Common.Exceptions +@using Modmail.NET.Database.Entities +@using Modmail.NET.Features.Blacklist.Commands +@using Modmail.NET.Web.Blazor.Extensions @using Serilog @inject IDbContextFactory DbContextFactory @inject NotificationService NotificationService @@ -67,7 +68,7 @@ _reason); DialogService.Close(true); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { ex.NotifyException(NotificationService); Log.Warning(logMessage, _selectedUserId, diff --git a/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/AddRoleToTeamDialog.razor b/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/AddRoleToTeamDialog.razor index 0d08043e..20bf66ba 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/AddRoleToTeamDialog.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/AddRoleToTeamDialog.razor @@ -1,10 +1,11 @@ @using DSharpPlus.Entities @using Microsoft.EntityFrameworkCore -@using Modmail.NET.Abstract -@using Modmail.NET.Extensions -@using Modmail.NET.Features.Bot -@using Modmail.NET.Features.Teams -@using Modmail.NET.Static +@using Modmail.NET.Common.Exceptions +@using Modmail.NET.Database.Entities +@using Modmail.NET.Features.DiscordBot.Queries +@using Modmail.NET.Features.Teams.Commands +@using Modmail.NET.Features.Teams.Static +@using Modmail.NET.Web.Blazor.Extensions @using Serilog @inject IDbContextFactory DbContextFactory @inject NotificationService NotificationService @@ -60,7 +61,7 @@ NotificationService.Notify(NotificationSeverity.Success, "Success", "Role added to team successfully."); DialogService.Close(true); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning(logMessage, _selectedRoleId); ex.NotifyException(NotificationService); } diff --git a/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/AddUserToTeamDialog.razor b/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/AddUserToTeamDialog.razor index f2d6fe25..657bfd6a 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/AddUserToTeamDialog.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/AddUserToTeamDialog.razor @@ -1,8 +1,9 @@ @using Microsoft.EntityFrameworkCore -@using Modmail.NET.Abstract -@using Modmail.NET.Extensions -@using Modmail.NET.Features.Teams -@using Modmail.NET.Static +@using Modmail.NET.Common.Exceptions +@using Modmail.NET.Database.Entities +@using Modmail.NET.Features.Teams.Commands +@using Modmail.NET.Features.Teams.Static +@using Modmail.NET.Web.Blazor.Extensions @using Serilog @inject IDbContextFactory DbContextFactory @inject NotificationService NotificationService @@ -61,7 +62,7 @@ NotificationService.Notify(NotificationSeverity.Success, "Success", "User added to team successfully."); DialogService.Close(true); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning(logMessage, _selectedUserId); ex.NotifyException(NotificationService); } diff --git a/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/CreateOrUpdateTeamDialog.razor b/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/CreateOrUpdateTeamDialog.razor index 5b5fc9e4..2a5f25a4 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/CreateOrUpdateTeamDialog.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/CreateOrUpdateTeamDialog.razor @@ -1,10 +1,11 @@ @using Microsoft.EntityFrameworkCore @using Microsoft.Extensions.Options -@using Modmail.NET.Abstract -@using Modmail.NET.Extensions -@using Modmail.NET.Features.Permission -@using Modmail.NET.Features.Teams -@using Modmail.NET.Static +@using Modmail.NET.Common.Exceptions +@using Modmail.NET.Database.Entities +@using Modmail.NET.Features.Permission.Queries +@using Modmail.NET.Features.Permission.Static +@using Modmail.NET.Features.Teams.Commands +@using Modmail.NET.Web.Blazor.Extensions @using Serilog @inject IDbContextFactory DbContextFactory @inject NotificationService NotificationService @@ -96,7 +97,7 @@ DialogService.Close(true); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning(ex, logMessage, _teamName, _permissionLevel); ex.NotifyException(NotificationService); } diff --git a/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/TeamDetailsDialog.razor b/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/TeamDetailsDialog.razor index 151407c3..5c4c654f 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/TeamDetailsDialog.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/TeamDetailsDialog.razor @@ -1,10 +1,11 @@ @using DSharpPlus.Entities @using Microsoft.EntityFrameworkCore -@using Modmail.NET.Abstract -@using Modmail.NET.Extensions -@using Modmail.NET.Features.Bot -@using Modmail.NET.Features.Teams -@using Modmail.NET.Static +@using Modmail.NET.Common.Exceptions +@using Modmail.NET.Database.Entities +@using Modmail.NET.Features.DiscordBot.Queries +@using Modmail.NET.Features.Teams.Commands +@using Modmail.NET.Features.Teams.Static +@using Modmail.NET.Web.Blazor.Extensions @using Serilog @inject IDbContextFactory DbContextFactory @inject TooltipService TooltipService @@ -83,7 +84,7 @@ NotificationService.Notify(NotificationSeverity.Success, "Success", "Team member removed"); await ReloadDataAsync(); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning(ex, logMessage, guildTeamMember); ex.NotifyException(NotificationService); } diff --git a/src/Modmail.NET.Web.Blazor/Components/Shared/TicketType/CreateOrUpdateTicketTypeDialog.razor b/src/Modmail.NET.Web.Blazor/Components/Shared/TicketType/CreateOrUpdateTicketTypeDialog.razor index 512b99b5..788f140a 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Shared/TicketType/CreateOrUpdateTicketTypeDialog.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Shared/TicketType/CreateOrUpdateTicketTypeDialog.razor @@ -1,7 +1,8 @@ @using DSharpPlus.Entities -@using Modmail.NET.Abstract -@using Modmail.NET.Extensions -@using Modmail.NET.Features.TicketType +@using Modmail.NET.Common.Exceptions +@using Modmail.NET.Database.Entities +@using Modmail.NET.Features.Ticket.Commands +@using Modmail.NET.Web.Blazor.Extensions @using Serilog @inject DialogService DialogService @inject NotificationService NotificationService @@ -104,7 +105,7 @@ DialogService.Close(true); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning(ex, logMessage, _name); ex.NotifyException(NotificationService); } diff --git a/src/Modmail.NET.Web.Blazor/Components/Shared/Tickets/TicketNotes.razor b/src/Modmail.NET.Web.Blazor/Components/Shared/Tickets/TicketNotes.razor index 0974b0fb..a27a04a8 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Shared/Tickets/TicketNotes.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Shared/Tickets/TicketNotes.razor @@ -1,5 +1,6 @@ @using Microsoft.EntityFrameworkCore -@using Modmail.NET.Features.UserInfo +@using Modmail.NET.Database.Entities +@using Modmail.NET.Features.User.Queries @inject IDbContextFactory DbContextFactory @inject ISender Sender diff --git a/src/Modmail.NET.Web.Blazor/Components/_Imports.razor b/src/Modmail.NET.Web.Blazor/Components/_Imports.razor index d90563f6..605e521a 100644 --- a/src/Modmail.NET.Web.Blazor/Components/_Imports.razor +++ b/src/Modmail.NET.Web.Blazor/Components/_Imports.razor @@ -14,9 +14,4 @@ @using Radzen.Blazor.Rendering @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization -@using Modmail.NET.Models.Dto -@using Modmail.NET.Models -@using Modmail.NET.Entities @using Modmail.NET.Database -@using Modmail.NET.Exceptions -@using Modmail.NET.Mappers \ No newline at end of file diff --git a/src/Modmail.NET.Web.Blazor/Controllers/AttachmentController.cs b/src/Modmail.NET.Web.Blazor/Controllers/AttachmentController.cs index d292777e..a7cac110 100644 --- a/src/Modmail.NET.Web.Blazor/Controllers/AttachmentController.cs +++ b/src/Modmail.NET.Web.Blazor/Controllers/AttachmentController.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Modmail.NET.Database; -using Modmail.NET.Static; +using Modmail.NET.Features.Ticket.Services; namespace Modmail.NET.Web.Blazor.Controllers; @@ -27,7 +27,7 @@ public async Task Get(Guid id) { if (attachment is null) return NotFound(); var extension = Path.GetExtension(attachment.FileName) ?? throw new NullReferenceException("extension"); //starts with . - var filePath = Path.Combine(Const.AttachmentDownloadDirectory, id + extension); + var filePath = Path.Combine(TicketAttachmentDownloadService.AttachmentDownloadDirectory, id + extension); var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); return new FileStreamResult(stream, attachment.MediaType); } diff --git a/src/Modmail.NET.Web.Blazor/Dependency/AuthDependency.cs b/src/Modmail.NET.Web.Blazor/Dependency/AuthDependency.cs index 5723c503..08bcee3b 100644 --- a/src/Modmail.NET.Web.Blazor/Dependency/AuthDependency.cs +++ b/src/Modmail.NET.Web.Blazor/Dependency/AuthDependency.cs @@ -4,10 +4,10 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Components.Authorization; -using Modmail.NET.Abstract; -using Modmail.NET.Features.Permission; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Static; +using Modmail.NET.Features.Permission.Queries; using Modmail.NET.Language; -using Modmail.NET.Static; using Modmail.NET.Web.Blazor.Providers; using Serilog; @@ -79,7 +79,7 @@ public static void Configure(WebApplicationBuilder builder) { Log.Information("Discord.OAuth access granted {UserId} {UserName} {Permission}", userId, context.Principal.FindFirst(ClaimTypes.Name), permission.ToString()); context.Success(); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning(ex, "Discord.OAuth Access failed {UserId}", userId); context.Fail(ex.TitleMessage + " : " + ex.ContentMessage); } diff --git a/src/Modmail.NET.Web.Blazor/Dependency/BlazorDependency.cs b/src/Modmail.NET.Web.Blazor/Dependency/BlazorDependency.cs index 7b1630df..d40392e9 100644 --- a/src/Modmail.NET.Web.Blazor/Dependency/BlazorDependency.cs +++ b/src/Modmail.NET.Web.Blazor/Dependency/BlazorDependency.cs @@ -1,4 +1,4 @@ -using Modmail.NET.Static; +using Modmail.NET.Common.Static; using Radzen; namespace Modmail.NET.Web.Blazor.Dependency; diff --git a/src/Modmail.NET.Web.Blazor/Dependency/BusinessDependency.cs b/src/Modmail.NET.Web.Blazor/Dependency/BusinessDependency.cs index 83194844..aa8b2ea1 100644 --- a/src/Modmail.NET.Web.Blazor/Dependency/BusinessDependency.cs +++ b/src/Modmail.NET.Web.Blazor/Dependency/BusinessDependency.cs @@ -1,11 +1,10 @@ using EntityFramework.Exceptions.SqlServer; using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Static; using Modmail.NET.Database; using Modmail.NET.Database.Triggers; +using Modmail.NET.Features.Ticket.Services; using Modmail.NET.Language; -using Modmail.NET.Queues; -using Modmail.NET.Services; -using Modmail.NET.Static; using Modmail.NET.Web.Blazor.Services; using Serilog; using Serilog.Extensions.Logging; @@ -26,7 +25,7 @@ public static void Configure(WebApplicationBuilder builder) { builder.Services.Configure(botConfig); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); diff --git a/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs b/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs index fa804cae..be38d661 100644 --- a/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs +++ b/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs @@ -3,9 +3,8 @@ using DSharpPlus.Commands.Processors.TextCommands; using DSharpPlus.Commands.Processors.TextCommands.Parsing; using DSharpPlus.Extensions; -using Modmail.NET.Commands; -using Modmail.NET.Commands.Slash; -using Modmail.NET.Events; +using Modmail.NET.Features.DiscordBot.Events; +using Modmail.NET.Features.DiscordCommands.Handlers; using Serilog; namespace Modmail.NET.Web.Blazor.Dependency; diff --git a/src/Modmail.NET.Web.Blazor/Dependency/HangfireDependency.cs b/src/Modmail.NET.Web.Blazor/Dependency/HangfireDependency.cs index a2af9441..49b9d718 100644 --- a/src/Modmail.NET.Web.Blazor/Dependency/HangfireDependency.cs +++ b/src/Modmail.NET.Web.Blazor/Dependency/HangfireDependency.cs @@ -1,6 +1,6 @@ using Hangfire; using Modmail.NET.Abstract; -using Modmail.NET.Static; +using Modmail.NET.Common.Static; using Modmail.NET.Web.Blazor.Providers; using Serilog; diff --git a/src/Modmail.NET/Extensions/ExtWeb.cs b/src/Modmail.NET.Web.Blazor/Extensions/WebExtensions.cs similarity index 82% rename from src/Modmail.NET/Extensions/ExtWeb.cs rename to src/Modmail.NET.Web.Blazor/Extensions/WebExtensions.cs index dcd880ce..94d4088f 100644 --- a/src/Modmail.NET/Extensions/ExtWeb.cs +++ b/src/Modmail.NET.Web.Blazor/Extensions/WebExtensions.cs @@ -1,8 +1,8 @@ using System.Security.Claims; -namespace Modmail.NET.Extensions; +namespace Modmail.NET.Web.Blazor.Extensions; -public static class ExtWeb +public static class WebExtensions { public static ulong GetUserId(this ClaimsPrincipal principal) { var nameId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value; diff --git a/src/Modmail.NET.Web.Blazor/Program.cs b/src/Modmail.NET.Web.Blazor/Program.cs index b230d361..f70a16db 100644 --- a/src/Modmail.NET.Web.Blazor/Program.cs +++ b/src/Modmail.NET.Web.Blazor/Program.cs @@ -1,6 +1,6 @@ using Modmail.NET; +using Modmail.NET.Common.Utils; using Modmail.NET.Language; -using Modmail.NET.Utils; using Modmail.NET.Web.Blazor.Components; using Modmail.NET.Web.Blazor.Dependency; using Serilog; diff --git a/src/Modmail.NET.Web.Blazor/Providers/AuthorizeTeamAttribute.cs b/src/Modmail.NET.Web.Blazor/Providers/AuthorizeTeamAttribute.cs index 849154c2..f9a9273f 100644 --- a/src/Modmail.NET.Web.Blazor/Providers/AuthorizeTeamAttribute.cs +++ b/src/Modmail.NET.Web.Blazor/Providers/AuthorizeTeamAttribute.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Authorization; -using Modmail.NET.Static; +using Modmail.NET.Common.Static; +using Modmail.NET.Features.Permission.Static; namespace Modmail.NET.Web.Blazor.Providers; diff --git a/src/Modmail.NET.Web.Blazor/Providers/HangfireAuthorizationProvider.cs b/src/Modmail.NET.Web.Blazor/Providers/HangfireAuthorizationProvider.cs index 524f9cd0..28e222a5 100644 --- a/src/Modmail.NET.Web.Blazor/Providers/HangfireAuthorizationProvider.cs +++ b/src/Modmail.NET.Web.Blazor/Providers/HangfireAuthorizationProvider.cs @@ -1,6 +1,6 @@ using Hangfire.Dashboard; using Microsoft.AspNetCore.Authorization; -using Modmail.NET.Static; +using Modmail.NET.Common.Static; namespace Modmail.NET.Web.Blazor.Providers; diff --git a/src/Modmail.NET.Web.Blazor/Providers/TeamPermissionCheckHandler.cs b/src/Modmail.NET.Web.Blazor/Providers/TeamPermissionCheckHandler.cs index 07b798f9..b755037f 100644 --- a/src/Modmail.NET.Web.Blazor/Providers/TeamPermissionCheckHandler.cs +++ b/src/Modmail.NET.Web.Blazor/Providers/TeamPermissionCheckHandler.cs @@ -1,8 +1,8 @@ using System.Security.Claims; using Microsoft.AspNetCore.Authorization; -using Modmail.NET.Extensions; -using Modmail.NET.Features.Permission; -using Modmail.NET.Static; +using Modmail.NET.Features.Permission.Queries; +using Modmail.NET.Features.Permission.Static; +using Modmail.NET.Web.Blazor.Extensions; namespace Modmail.NET.Web.Blazor.Providers; diff --git a/src/Modmail.NET.Web.Blazor/Providers/TeamPermissionCheckRequirement.cs b/src/Modmail.NET.Web.Blazor/Providers/TeamPermissionCheckRequirement.cs index 5e41bcff..4a26d21f 100644 --- a/src/Modmail.NET.Web.Blazor/Providers/TeamPermissionCheckRequirement.cs +++ b/src/Modmail.NET.Web.Blazor/Providers/TeamPermissionCheckRequirement.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Authorization; -using Modmail.NET.Static; +using Modmail.NET.Common.Static; namespace Modmail.NET.Web.Blazor.Providers; diff --git a/src/Modmail.NET.Web.Blazor/RadzenTools.cs b/src/Modmail.NET.Web.Blazor/RadzenTools.cs index 26336e72..90d92a76 100644 --- a/src/Modmail.NET.Web.Blazor/RadzenTools.cs +++ b/src/Modmail.NET.Web.Blazor/RadzenTools.cs @@ -1,5 +1,5 @@ using System.Linq.Dynamic.Core; -using Modmail.NET.Abstract; +using Modmail.NET.Common.Exceptions; using Radzen; namespace Modmail.NET.Web.Blazor; @@ -34,7 +34,7 @@ public static IQueryable ApplyPagination(this IQueryable queryable, int } public static void NotifyException(this T exception, NotificationService service, bool showExceptionMessage = false) where T : Exception { - if (exception is BotExceptionBase botException) { + if (exception is ModmailBotException botException) { service.Notify(NotificationSeverity.Warning, "Failed", botException.TitleMessage); return; } diff --git a/src/Modmail.NET/Abstract/BotExceptionBase.cs b/src/Modmail.NET/Abstract/BotExceptionBase.cs deleted file mode 100644 index dc0dc53b..00000000 --- a/src/Modmail.NET/Abstract/BotExceptionBase.cs +++ /dev/null @@ -1,26 +0,0 @@ -using DSharpPlus.Entities; - -namespace Modmail.NET.Abstract; - -public abstract class BotExceptionBase : Exception -{ - protected BotExceptionBase(string titleMessage, string contentMessage = null) { - TitleMessage = titleMessage; - ContentMessage = contentMessage; - } - - public string TitleMessage { get; } - public string ContentMessage { get; } - - public DiscordWebhookBuilder GetWebhookResponse() { - return Webhooks.Warning(TitleMessage, ContentMessage ?? ""); - } - - public DiscordEmbedBuilder GetEmbedResponse() { - return Embeds.Warning(TitleMessage, ContentMessage ?? ""); - } - - public DiscordInteractionResponseBuilder GetInteractionResponse() { - return Interactions.Warning(TitleMessage, ContentMessage ?? ""); - } -} \ No newline at end of file diff --git a/src/Modmail.NET/Abstract/IEntity.cs b/src/Modmail.NET/Abstract/IEntity.cs deleted file mode 100644 index cb35a6f6..00000000 --- a/src/Modmail.NET/Abstract/IEntity.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Modmail.NET.Abstract; - -public interface IEntity { } \ No newline at end of file diff --git a/src/Modmail.NET/Abstract/BaseQueue.cs b/src/Modmail.NET/Abstract/MemoryQueueBase.cs similarity index 92% rename from src/Modmail.NET/Abstract/BaseQueue.cs rename to src/Modmail.NET/Abstract/MemoryQueueBase.cs index b995fc53..9a708598 100644 --- a/src/Modmail.NET/Abstract/BaseQueue.cs +++ b/src/Modmail.NET/Abstract/MemoryQueueBase.cs @@ -1,17 +1,17 @@ using System.Collections.Concurrent; using System.Diagnostics; using System.Threading.Channels; -using Modmail.NET.Utils; +using Modmail.NET.Common.Utils; namespace Modmail.NET.Abstract; -public abstract class BaseQueue where TKey : notnull +public abstract class MemoryQueueBase where TKey : notnull { private readonly TimeSpan _idleTimeout; private readonly ConcurrentDictionary _lastActiveTime = new(); private readonly ConcurrentDictionary> _queues = new(); - protected BaseQueue(TimeSpan idleTimeout) { + protected MemoryQueueBase(TimeSpan idleTimeout) { _idleTimeout = idleTimeout; } diff --git a/src/Modmail.NET/BotConfig.cs b/src/Modmail.NET/BotConfig.cs index b4af8e0a..d7292e34 100644 --- a/src/Modmail.NET/BotConfig.cs +++ b/src/Modmail.NET/BotConfig.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Modmail.NET.Common.Static; namespace Modmail.NET; diff --git a/src/Modmail.NET/Aspects/PerformanceLoggerAspect.cs b/src/Modmail.NET/Common/Aspects/PerformanceLoggerAspect.cs similarity index 96% rename from src/Modmail.NET/Aspects/PerformanceLoggerAspect.cs rename to src/Modmail.NET/Common/Aspects/PerformanceLoggerAspect.cs index 5faef0fd..89a065bd 100644 --- a/src/Modmail.NET/Aspects/PerformanceLoggerAspect.cs +++ b/src/Modmail.NET/Common/Aspects/PerformanceLoggerAspect.cs @@ -2,7 +2,7 @@ using AspectInjector.Broker; using Serilog; -namespace Modmail.NET.Aspects; +namespace Modmail.NET.Common.Aspects; /// /// Performance Logger Aspect for logging long running actions, with a default threshold of 1000 ms. Desired threshold diff --git a/src/Modmail.NET/Common/Exceptions/AnotherServerAlreadySetupException.cs b/src/Modmail.NET/Common/Exceptions/AnotherServerAlreadySetupException.cs new file mode 100644 index 00000000..730083c9 --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/AnotherServerAlreadySetupException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class AnotherServerAlreadySetupException : ModmailBotException +{ + public AnotherServerAlreadySetupException() : base(LangProvider.This.GetTranslation(LangKeys.AnotherServerAlreadySetup)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/CanNotDeleteTicketTypeWhenActiveTicketsException.cs b/src/Modmail.NET/Common/Exceptions/CanNotDeleteTicketTypeWhenActiveTicketsException.cs similarity index 69% rename from src/Modmail.NET/Exceptions/CanNotDeleteTicketTypeWhenActiveTicketsException.cs rename to src/Modmail.NET/Common/Exceptions/CanNotDeleteTicketTypeWhenActiveTicketsException.cs index 90e55ff1..efd9cf55 100644 --- a/src/Modmail.NET/Exceptions/CanNotDeleteTicketTypeWhenActiveTicketsException.cs +++ b/src/Modmail.NET/Common/Exceptions/CanNotDeleteTicketTypeWhenActiveTicketsException.cs @@ -1,8 +1,8 @@ -using Modmail.NET.Abstract; +using Modmail.NET.Language; -namespace Modmail.NET.Exceptions; +namespace Modmail.NET.Common.Exceptions; -public class CanNotDeleteTicketTypeWhenActiveTicketsException : BotExceptionBase +public class CanNotDeleteTicketTypeWhenActiveTicketsException : ModmailBotException { public CanNotDeleteTicketTypeWhenActiveTicketsException() : base(LangKeys.CanNotDeleteTicketTypeWhenActiveTickets.GetTranslation()) { } } \ No newline at end of file diff --git a/src/Modmail.NET/Common/Exceptions/DbInternalException.cs b/src/Modmail.NET/Common/Exceptions/DbInternalException.cs new file mode 100644 index 00000000..b3015f0d --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/DbInternalException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class DbInternalException : ModmailBotException +{ + public DbInternalException() : base(LangProvider.This.GetTranslation(LangKeys.DbInternalError)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/EmptyListResultException.cs b/src/Modmail.NET/Common/Exceptions/EmptyListResultException.cs similarity index 54% rename from src/Modmail.NET/Exceptions/EmptyListResultException.cs rename to src/Modmail.NET/Common/Exceptions/EmptyListResultException.cs index 7252d9ef..ef8e3841 100644 --- a/src/Modmail.NET/Exceptions/EmptyListResultException.cs +++ b/src/Modmail.NET/Common/Exceptions/EmptyListResultException.cs @@ -1,8 +1,8 @@ -using Modmail.NET.Abstract; +using Modmail.NET.Language; -namespace Modmail.NET.Exceptions; +namespace Modmail.NET.Common.Exceptions; -public class EmptyListResultException : BotExceptionBase +public class EmptyListResultException : ModmailBotException { public EmptyListResultException(LangKeys name) : base(LangKeys.NoXFound.GetTranslation(name)) { Name = name; diff --git a/src/Modmail.NET/Common/Exceptions/InvalidInteractionKeyException.cs b/src/Modmail.NET/Common/Exceptions/InvalidInteractionKeyException.cs new file mode 100644 index 00000000..3f1a040e --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/InvalidInteractionKeyException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class InvalidInteractionKeyException : ModmailBotException +{ + public InvalidInteractionKeyException() : base(LangProvider.This.GetTranslation(LangKeys.InvalidInteractionKey)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Common/Exceptions/InvalidMessageIdException.cs b/src/Modmail.NET/Common/Exceptions/InvalidMessageIdException.cs new file mode 100644 index 00000000..7f6c3ea3 --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/InvalidMessageIdException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class InvalidMessageIdException : ModmailBotException +{ + public InvalidMessageIdException() : base(LangProvider.This.GetTranslation(LangKeys.InvalidMessageId)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/InvalidNameException.cs b/src/Modmail.NET/Common/Exceptions/InvalidNameException.cs similarity index 56% rename from src/Modmail.NET/Exceptions/InvalidNameException.cs rename to src/Modmail.NET/Common/Exceptions/InvalidNameException.cs index a7a3ad21..145e0f71 100644 --- a/src/Modmail.NET/Exceptions/InvalidNameException.cs +++ b/src/Modmail.NET/Common/Exceptions/InvalidNameException.cs @@ -1,8 +1,8 @@ -using Modmail.NET.Abstract; +using Modmail.NET.Language; -namespace Modmail.NET.Exceptions; +namespace Modmail.NET.Common.Exceptions; -public class InvalidNameException : BotExceptionBase +public class InvalidNameException : ModmailBotException { public InvalidNameException(string name) : base(LangProvider.This.GetTranslation(LangKeys.InvalidName)) { Name = name; diff --git a/src/Modmail.NET/Common/Exceptions/InvalidUserIdException.cs b/src/Modmail.NET/Common/Exceptions/InvalidUserIdException.cs new file mode 100644 index 00000000..ce5fcf81 --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/InvalidUserIdException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class InvalidUserIdException : ModmailBotException +{ + public InvalidUserIdException() : base(LangProvider.This.GetTranslation(LangKeys.InvalidUser)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Common/Exceptions/MainServerAlreadySetupException.cs b/src/Modmail.NET/Common/Exceptions/MainServerAlreadySetupException.cs new file mode 100644 index 00000000..bd5eac04 --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/MainServerAlreadySetupException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class MainServerAlreadySetupException : ModmailBotException +{ + public MainServerAlreadySetupException() : base(LangProvider.This.GetTranslation(LangKeys.MainServerAlreadySetup)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Common/Exceptions/MemberAlreadyInTeamException.cs b/src/Modmail.NET/Common/Exceptions/MemberAlreadyInTeamException.cs new file mode 100644 index 00000000..ea6ec5eb --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/MemberAlreadyInTeamException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class MemberAlreadyInTeamException : ModmailBotException +{ + public MemberAlreadyInTeamException() : base(LangProvider.This.GetTranslation(LangKeys.MemberAlreadyInTeam)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Common/Exceptions/ModmailBotException.cs b/src/Modmail.NET/Common/Exceptions/ModmailBotException.cs new file mode 100644 index 00000000..5d6d2c65 --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/ModmailBotException.cs @@ -0,0 +1,27 @@ +using DSharpPlus.Entities; +using Modmail.NET.Common.Static; + +namespace Modmail.NET.Common.Exceptions; + +public abstract class ModmailBotException : Exception +{ + protected ModmailBotException(string titleMessage, string contentMessage = null) { + TitleMessage = titleMessage; + ContentMessage = contentMessage; + } + + public string TitleMessage { get; } + public string ContentMessage { get; } + + public DiscordWebhookBuilder GetWebhookResponse() { + return ModmailWebhooks.Warning(TitleMessage, ContentMessage ?? ""); + } + + public DiscordEmbedBuilder GetEmbedResponse() { + return ModmailEmbeds.Warning(TitleMessage, ContentMessage ?? ""); + } + + public DiscordInteractionResponseBuilder GetInteractionResponse() { + return ModmailInteractions.Warning(TitleMessage, ContentMessage ?? ""); + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/NotFoundException.cs b/src/Modmail.NET/Common/Exceptions/NotFoundException.cs similarity index 57% rename from src/Modmail.NET/Exceptions/NotFoundException.cs rename to src/Modmail.NET/Common/Exceptions/NotFoundException.cs index 221cc597..7d55e22a 100644 --- a/src/Modmail.NET/Exceptions/NotFoundException.cs +++ b/src/Modmail.NET/Common/Exceptions/NotFoundException.cs @@ -1,8 +1,8 @@ -using Modmail.NET.Abstract; +using Modmail.NET.Language; -namespace Modmail.NET.Exceptions; +namespace Modmail.NET.Common.Exceptions; -public class NotFoundException : BotExceptionBase +public class NotFoundException : ModmailBotException { public NotFoundException(LangKeys name) : base(LangProvider.This.GetTranslation(LangKeys.XNotFound, name)) { Name = name; diff --git a/src/Modmail.NET/Exceptions/NotFoundInException.cs b/src/Modmail.NET/Common/Exceptions/NotFoundInException.cs similarity index 67% rename from src/Modmail.NET/Exceptions/NotFoundInException.cs rename to src/Modmail.NET/Common/Exceptions/NotFoundInException.cs index 2c302935..4329ddc5 100644 --- a/src/Modmail.NET/Exceptions/NotFoundInException.cs +++ b/src/Modmail.NET/Common/Exceptions/NotFoundInException.cs @@ -1,8 +1,8 @@ -using Modmail.NET.Abstract; +using Modmail.NET.Language; -namespace Modmail.NET.Exceptions; +namespace Modmail.NET.Common.Exceptions; -public class NotFoundInException : BotExceptionBase +public class NotFoundInException : ModmailBotException { public NotFoundInException(LangKeys name, LangKeys inName) : base(LangProvider.This.GetTranslation(LangKeys.XNotFoundInY, name, inName)) { Name = name; diff --git a/src/Modmail.NET/Exceptions/NotFoundWithException.cs b/src/Modmail.NET/Common/Exceptions/NotFoundWithException.cs similarity index 64% rename from src/Modmail.NET/Exceptions/NotFoundWithException.cs rename to src/Modmail.NET/Common/Exceptions/NotFoundWithException.cs index 786adff6..d79a8960 100644 --- a/src/Modmail.NET/Exceptions/NotFoundWithException.cs +++ b/src/Modmail.NET/Common/Exceptions/NotFoundWithException.cs @@ -1,8 +1,8 @@ -using Modmail.NET.Abstract; +using Modmail.NET.Language; -namespace Modmail.NET.Exceptions; +namespace Modmail.NET.Common.Exceptions; -public class NotFoundWithException : BotExceptionBase +public class NotFoundWithException : ModmailBotException { public NotFoundWithException(LangKeys name, object id) : base(LangProvider.This.GetTranslation(LangKeys.XNotFound, name, id)) { Name = name; diff --git a/src/Modmail.NET/Common/Exceptions/NotJoinedMainServerException.cs b/src/Modmail.NET/Common/Exceptions/NotJoinedMainServerException.cs new file mode 100644 index 00000000..b77fd0f1 --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/NotJoinedMainServerException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class NotJoinedMainServerException : ModmailBotException +{ + public NotJoinedMainServerException() : base(LangProvider.This.GetTranslation(LangKeys.NotJoinedMainServer)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Common/Exceptions/RoleAlreadyInTeamException.cs b/src/Modmail.NET/Common/Exceptions/RoleAlreadyInTeamException.cs new file mode 100644 index 00000000..e9ecb76e --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/RoleAlreadyInTeamException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class RoleAlreadyInTeamException : ModmailBotException +{ + public RoleAlreadyInTeamException() : base(LangProvider.This.GetTranslation(LangKeys.RoleAlreadyInTeam)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Common/Exceptions/ServerIsNotSetupException.cs b/src/Modmail.NET/Common/Exceptions/ServerIsNotSetupException.cs new file mode 100644 index 00000000..b183e825 --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/ServerIsNotSetupException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class ServerIsNotSetupException : ModmailBotException +{ + public ServerIsNotSetupException() : base(LangProvider.This.GetTranslation(LangKeys.RoleNotFoundInTeam)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Common/Exceptions/TeamAlreadyExistsException.cs b/src/Modmail.NET/Common/Exceptions/TeamAlreadyExistsException.cs new file mode 100644 index 00000000..8d345f97 --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/TeamAlreadyExistsException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class TeamAlreadyExistsException : ModmailBotException +{ + public TeamAlreadyExistsException() : base(LangProvider.This.GetTranslation(LangKeys.TeamAlreadyExists)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Common/Exceptions/TeamNotExistsException.cs b/src/Modmail.NET/Common/Exceptions/TeamNotExistsException.cs new file mode 100644 index 00000000..5949a98f --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/TeamNotExistsException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class TeamNotExistsException : ModmailBotException +{ + public TeamNotExistsException() : base(LangProvider.This.GetTranslation(LangKeys.TeamNotExists)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Common/Exceptions/TicketAlreadyClosedException.cs b/src/Modmail.NET/Common/Exceptions/TicketAlreadyClosedException.cs new file mode 100644 index 00000000..8ed17e6a --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/TicketAlreadyClosedException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class TicketAlreadyClosedException : ModmailBotException +{ + public TicketAlreadyClosedException() : base(LangProvider.This.GetTranslation(LangKeys.TicketAlreadyClosed)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Common/Exceptions/TicketMustBeClosedException.cs b/src/Modmail.NET/Common/Exceptions/TicketMustBeClosedException.cs new file mode 100644 index 00000000..1a77c3bc --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/TicketMustBeClosedException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class TicketMustBeClosedException : ModmailBotException +{ + public TicketMustBeClosedException() : base(LangProvider.This.GetTranslation(LangKeys.TicketMustBeClosed)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Common/Exceptions/TicketTimeoutOutOfRangeException.cs b/src/Modmail.NET/Common/Exceptions/TicketTimeoutOutOfRangeException.cs new file mode 100644 index 00000000..26c33d26 --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/TicketTimeoutOutOfRangeException.cs @@ -0,0 +1,11 @@ +using Modmail.NET.Features.Ticket.Static; +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class TicketTimeoutOutOfRangeException : ModmailBotException +{ + public TicketTimeoutOutOfRangeException() : base(LangKeys.TicketTimeoutValueIsOutOfRange.GetTranslation(), + LangKeys.TicketTimeoutValueMustBeBetweenXAndY.GetTranslation(TicketConstants.TicketTimeoutMinAllowedHours, + TicketConstants.TicketTimeoutMaxAllowedHours)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/TicketTypeAlreadyExistsException.cs b/src/Modmail.NET/Common/Exceptions/TicketTypeAlreadyExistsException.cs similarity index 57% rename from src/Modmail.NET/Exceptions/TicketTypeAlreadyExistsException.cs rename to src/Modmail.NET/Common/Exceptions/TicketTypeAlreadyExistsException.cs index a71eec0c..e73b1881 100644 --- a/src/Modmail.NET/Exceptions/TicketTypeAlreadyExistsException.cs +++ b/src/Modmail.NET/Common/Exceptions/TicketTypeAlreadyExistsException.cs @@ -1,8 +1,8 @@ -using Modmail.NET.Abstract; +using Modmail.NET.Language; -namespace Modmail.NET.Exceptions; +namespace Modmail.NET.Common.Exceptions; -public class TicketTypeAlreadyExistsException : BotExceptionBase +public class TicketTypeAlreadyExistsException : ModmailBotException { public TicketTypeAlreadyExistsException(string name) : base(LangProvider.This.GetTranslation(LangKeys.TicketTypeAlreadyExists)) { Name = name; diff --git a/src/Modmail.NET/Exceptions/TicketTypeNotExistsException.cs b/src/Modmail.NET/Common/Exceptions/TicketTypeNotExistsException.cs similarity index 58% rename from src/Modmail.NET/Exceptions/TicketTypeNotExistsException.cs rename to src/Modmail.NET/Common/Exceptions/TicketTypeNotExistsException.cs index 50cd2484..4ba2aeb1 100644 --- a/src/Modmail.NET/Exceptions/TicketTypeNotExistsException.cs +++ b/src/Modmail.NET/Common/Exceptions/TicketTypeNotExistsException.cs @@ -1,8 +1,8 @@ -using Modmail.NET.Abstract; +using Modmail.NET.Language; -namespace Modmail.NET.Exceptions; +namespace Modmail.NET.Common.Exceptions; -public class TicketTypeNotExistsException : BotExceptionBase +public class TicketTypeNotExistsException : ModmailBotException { public TicketTypeNotExistsException(string name = null) : base(LangProvider.This.GetTranslation(LangKeys.TicketTypeNotExists)) { Name = name; diff --git a/src/Modmail.NET/Common/Exceptions/UserAlreadyBlacklistedException.cs b/src/Modmail.NET/Common/Exceptions/UserAlreadyBlacklistedException.cs new file mode 100644 index 00000000..883098ec --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/UserAlreadyBlacklistedException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class UserAlreadyBlacklistedException : ModmailBotException +{ + public UserAlreadyBlacklistedException() : base(LangProvider.This.GetTranslation(LangKeys.UserAlreadyBlacklisted)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Common/Exceptions/UserIsNotBlacklistedException.cs b/src/Modmail.NET/Common/Exceptions/UserIsNotBlacklistedException.cs new file mode 100644 index 00000000..2f72a2ab --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/UserIsNotBlacklistedException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class UserIsNotBlacklistedException : ModmailBotException +{ + public UserIsNotBlacklistedException() : base(LangProvider.This.GetTranslation(LangKeys.UserIsNotBlacklisted)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Extensions/ExtDiscordUser.cs b/src/Modmail.NET/Common/Extensions/DiscordUserExtensions.cs similarity index 87% rename from src/Modmail.NET/Extensions/ExtDiscordUser.cs rename to src/Modmail.NET/Common/Extensions/DiscordUserExtensions.cs index 2b3cd13e..7df1d3d9 100644 --- a/src/Modmail.NET/Extensions/ExtDiscordUser.cs +++ b/src/Modmail.NET/Common/Extensions/DiscordUserExtensions.cs @@ -1,8 +1,8 @@ using DSharpPlus.Entities; -namespace Modmail.NET.Extensions; +namespace Modmail.NET.Common.Extensions; -public static class ExtDiscordUser +public static class DiscordUserExtensions { public static string GetUsername(this DiscordUser user) { if (user == null) return string.Empty; diff --git a/src/Modmail.NET/Extensions/ExtEmbed.cs b/src/Modmail.NET/Common/Extensions/EmbedExtensions.cs similarity index 89% rename from src/Modmail.NET/Extensions/ExtEmbed.cs rename to src/Modmail.NET/Common/Extensions/EmbedExtensions.cs index 396d4e53..9f3083f2 100644 --- a/src/Modmail.NET/Extensions/ExtEmbed.cs +++ b/src/Modmail.NET/Common/Extensions/EmbedExtensions.cs @@ -1,11 +1,11 @@ using DSharpPlus.Entities; -using Modmail.NET.Entities; -using Modmail.NET.Features.Bot; -using Modmail.NET.Utils; +using Modmail.NET.Common.Utils; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.DiscordBot.Queries; -namespace Modmail.NET.Extensions; +namespace Modmail.NET.Common.Extensions; -public static class ExtEmbed +public static class EmbedExtensions { public static DiscordMessageBuilder AddAttachments(this DiscordMessageBuilder builder, TicketMessageAttachment[] attachments) { if (attachments == null || attachments.Length == 0) return builder; diff --git a/src/Modmail.NET/Common/Extensions/ExceptionExtensions.cs b/src/Modmail.NET/Common/Extensions/ExceptionExtensions.cs new file mode 100644 index 00000000..414df8a1 --- /dev/null +++ b/src/Modmail.NET/Common/Extensions/ExceptionExtensions.cs @@ -0,0 +1,45 @@ +using DSharpPlus.Entities; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Static; +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Extensions; + +public static class ExceptionExtensions +{ + public static DiscordWebhookBuilder ToWebhookResponse(this ModmailBotException exception) { + return ModmailWebhooks.Warning(exception.TitleMessage, exception.ContentMessage ?? ""); + } + + public static DiscordEmbedBuilder ToEmbedResponse(this ModmailBotException exception) { + return ModmailEmbeds.Warning(exception.TitleMessage, exception.ContentMessage ?? ""); + } + + public static DiscordInteractionResponseBuilder ToInteractionResponse(this ModmailBotException exception) { + return ModmailInteractions.Warning(exception.TitleMessage, exception.ContentMessage ?? ""); + } + + public static DiscordWebhookBuilder ToWebhookResponse(this Exception exception) { + var config = ServiceLocator.GetBotConfig(); + + if (config.Environment == EnvironmentType.Development) return ModmailWebhooks.Error(LangProvider.This.GetTranslation(LangKeys.AnExceptionOccurred), exception.Message); + + return ModmailWebhooks.Error(LangProvider.This.GetTranslation(LangKeys.AnExceptionOccurred)); + } + + public static DiscordEmbedBuilder ToEmbedResponse(this Exception exception) { + var config = ServiceLocator.GetBotConfig(); + + if (config.Environment == EnvironmentType.Development) return ModmailEmbeds.Error(LangProvider.This.GetTranslation(LangKeys.AnExceptionOccurred), exception.Message); + + return ModmailEmbeds.Error(LangProvider.This.GetTranslation(LangKeys.AnExceptionOccurred)); + } + + public static DiscordInteractionResponseBuilder ToInteractionResponse(this Exception exception) { + var config = ServiceLocator.GetBotConfig(); + + if (config.Environment == EnvironmentType.Development) return ModmailInteractions.Error(LangProvider.This.GetTranslation(LangKeys.AnExceptionOccurred), exception.Message); + + return ModmailInteractions.Error(LangProvider.This.GetTranslation(LangKeys.AnExceptionOccurred)); + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Extensions/ExtString.cs b/src/Modmail.NET/Common/Extensions/StringExtensions.cs similarity index 68% rename from src/Modmail.NET/Extensions/ExtString.cs rename to src/Modmail.NET/Common/Extensions/StringExtensions.cs index 0399d7b2..7ad821b1 100644 --- a/src/Modmail.NET/Extensions/ExtString.cs +++ b/src/Modmail.NET/Common/Extensions/StringExtensions.cs @@ -1,6 +1,6 @@ -namespace Modmail.NET.Extensions; +namespace Modmail.NET.Common.Extensions; -public static class ExtString +public static class StringExtensions { public static string GetStringOrNaN(this string value) { if (string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value)) return "NaN"; diff --git a/src/Modmail.NET/Static/AuthPolicy.cs b/src/Modmail.NET/Common/Static/AuthPolicy.cs similarity index 96% rename from src/Modmail.NET/Static/AuthPolicy.cs rename to src/Modmail.NET/Common/Static/AuthPolicy.cs index 025e0878..912f20cc 100644 --- a/src/Modmail.NET/Static/AuthPolicy.cs +++ b/src/Modmail.NET/Common/Static/AuthPolicy.cs @@ -1,6 +1,6 @@ using Ardalis.SmartEnum; -namespace Modmail.NET.Static; +namespace Modmail.NET.Common.Static; /// /// AuthPolicy smart enum diff --git a/src/Modmail.NET/Common/Static/Const.cs b/src/Modmail.NET/Common/Static/Const.cs new file mode 100644 index 00000000..5d91f193 --- /dev/null +++ b/src/Modmail.NET/Common/Static/Const.cs @@ -0,0 +1,10 @@ +using DSharpPlus.Entities; +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Static; + +public static class Const +{ + public const string ThemeCookieName = "Modmail.NET.Theme"; + public static readonly DiscordActivity DiscordActivity = new(LangProvider.This.GetTranslation(LangKeys.ModerationConcerns), DiscordActivityType.ListeningTo); +} \ No newline at end of file diff --git a/src/Modmail.NET/Static/DbLength.cs b/src/Modmail.NET/Common/Static/DbLength.cs similarity index 93% rename from src/Modmail.NET/Static/DbLength.cs rename to src/Modmail.NET/Common/Static/DbLength.cs index 8e44977a..d1e8db85 100644 --- a/src/Modmail.NET/Static/DbLength.cs +++ b/src/Modmail.NET/Common/Static/DbLength.cs @@ -1,4 +1,4 @@ -namespace Modmail.NET.Static; +namespace Modmail.NET.Common.Static; public static class DbLength { diff --git a/src/Modmail.NET/Static/DbType.cs b/src/Modmail.NET/Common/Static/DbType.cs similarity index 91% rename from src/Modmail.NET/Static/DbType.cs rename to src/Modmail.NET/Common/Static/DbType.cs index 6a6b1e1e..ee88e6f2 100644 --- a/src/Modmail.NET/Static/DbType.cs +++ b/src/Modmail.NET/Common/Static/DbType.cs @@ -1,6 +1,6 @@ using Ardalis.SmartEnum; -namespace Modmail.NET.Static; +namespace Modmail.NET.Common.Static; public sealed class DbType : SmartEnum { diff --git a/src/Modmail.NET/Static/EnvironmentType.cs b/src/Modmail.NET/Common/Static/EnvironmentType.cs similarity index 60% rename from src/Modmail.NET/Static/EnvironmentType.cs rename to src/Modmail.NET/Common/Static/EnvironmentType.cs index 725c5869..31ea242c 100644 --- a/src/Modmail.NET/Static/EnvironmentType.cs +++ b/src/Modmail.NET/Common/Static/EnvironmentType.cs @@ -1,4 +1,4 @@ -namespace Modmail.NET.Static; +namespace Modmail.NET.Common.Static; public enum EnvironmentType { diff --git a/src/Modmail.NET/Static/HangfireQueueName.cs b/src/Modmail.NET/Common/Static/HangfireQueueName.cs similarity index 90% rename from src/Modmail.NET/Static/HangfireQueueName.cs rename to src/Modmail.NET/Common/Static/HangfireQueueName.cs index f514e5c1..02352505 100644 --- a/src/Modmail.NET/Static/HangfireQueueName.cs +++ b/src/Modmail.NET/Common/Static/HangfireQueueName.cs @@ -1,6 +1,6 @@ using Ardalis.SmartEnum; -namespace Modmail.NET.Static; +namespace Modmail.NET.Common.Static; /// /// Hangfire queue names must be lower case diff --git a/src/Modmail.NET/Static/Colors.cs b/src/Modmail.NET/Common/Static/ModmailColors.cs similarity index 93% rename from src/Modmail.NET/Static/Colors.cs rename to src/Modmail.NET/Common/Static/ModmailColors.cs index 8e1c9e0c..9021de78 100644 --- a/src/Modmail.NET/Static/Colors.cs +++ b/src/Modmail.NET/Common/Static/ModmailColors.cs @@ -1,8 +1,8 @@ using DSharpPlus.Entities; -namespace Modmail.NET.Static; +namespace Modmail.NET.Common.Static; -public static class Colors +public static class ModmailColors { //Simple public static readonly DiscordColor ErrorColor = DiscordColor.DarkRed; diff --git a/src/Modmail.NET/Static/Embeds.cs b/src/Modmail.NET/Common/Static/ModmailEmbeds.cs similarity index 74% rename from src/Modmail.NET/Static/Embeds.cs rename to src/Modmail.NET/Common/Static/ModmailEmbeds.cs index 151dcbf0..ff85a255 100644 --- a/src/Modmail.NET/Static/Embeds.cs +++ b/src/Modmail.NET/Common/Static/ModmailEmbeds.cs @@ -1,34 +1,34 @@ using DSharpPlus.Entities; -namespace Modmail.NET.Static; +namespace Modmail.NET.Common.Static; -public static class Embeds +public static class ModmailEmbeds { public static DiscordEmbedBuilder Error(string title, string message = "") { return new DiscordEmbedBuilder() .WithTitle(title) .WithDescription(message) - .WithColor(Colors.ErrorColor); + .WithColor(ModmailColors.ErrorColor); } public static DiscordEmbedBuilder Success(string title, string message = "") { return new DiscordEmbedBuilder() .WithTitle(title) .WithDescription(message) - .WithColor(Colors.SuccessColor); + .WithColor(ModmailColors.SuccessColor); } public static DiscordEmbedBuilder Info(string title, string message = "") { return new DiscordEmbedBuilder() .WithTitle(title) .WithDescription(message) - .WithColor(Colors.InfoColor); + .WithColor(ModmailColors.InfoColor); } public static DiscordEmbedBuilder Warning(string title, string message = "") { return new DiscordEmbedBuilder() .WithTitle(title) .WithDescription(message) - .WithColor(Colors.WarningColor); + .WithColor(ModmailColors.WarningColor); } } \ No newline at end of file diff --git a/src/Modmail.NET/Common/Static/ModmailInteractions.cs b/src/Modmail.NET/Common/Static/ModmailInteractions.cs new file mode 100644 index 00000000..1e2ea030 --- /dev/null +++ b/src/Modmail.NET/Common/Static/ModmailInteractions.cs @@ -0,0 +1,22 @@ +using DSharpPlus.Entities; + +namespace Modmail.NET.Common.Static; + +public static class ModmailInteractions +{ + public static DiscordInteractionResponseBuilder Error(string title, string message = "") { + return new DiscordInteractionResponseBuilder().AddEmbed(ModmailEmbeds.Error(title, message)); + } + + public static DiscordInteractionResponseBuilder Success(string title, string message = "") { + return new DiscordInteractionResponseBuilder().AddEmbed(ModmailEmbeds.Success(title, message)); + } + + public static DiscordInteractionResponseBuilder Info(string title, string message = "") { + return new DiscordInteractionResponseBuilder().AddEmbed(ModmailEmbeds.Info(title, message)); + } + + public static DiscordInteractionResponseBuilder Warning(string title, string message = "") { + return new DiscordInteractionResponseBuilder().AddEmbed(ModmailEmbeds.Warning(title, message)); + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Common/Static/ModmailWebhooks.cs b/src/Modmail.NET/Common/Static/ModmailWebhooks.cs new file mode 100644 index 00000000..6f91e4d0 --- /dev/null +++ b/src/Modmail.NET/Common/Static/ModmailWebhooks.cs @@ -0,0 +1,22 @@ +using DSharpPlus.Entities; + +namespace Modmail.NET.Common.Static; + +public static class ModmailWebhooks +{ + public static DiscordWebhookBuilder Error(string title, string message = "") { + return new DiscordWebhookBuilder().AddEmbed(ModmailEmbeds.Error(title, message)); + } + + public static DiscordWebhookBuilder Success(string title, string message = "") { + return new DiscordWebhookBuilder().AddEmbed(ModmailEmbeds.Success(title, message)); + } + + public static DiscordWebhookBuilder Info(string title, string message = "") { + return new DiscordWebhookBuilder().AddEmbed(ModmailEmbeds.Info(title, message)); + } + + public static DiscordWebhookBuilder Warning(string title, string message = "") { + return new DiscordWebhookBuilder().AddEmbed(ModmailEmbeds.Warning(title, message)); + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Utils/UtilAttachment.cs b/src/Modmail.NET/Common/Utils/UtilAttachment.cs similarity index 56% rename from src/Modmail.NET/Utils/UtilAttachment.cs rename to src/Modmail.NET/Common/Utils/UtilAttachment.cs index c641b103..02540742 100644 --- a/src/Modmail.NET/Utils/UtilAttachment.cs +++ b/src/Modmail.NET/Common/Utils/UtilAttachment.cs @@ -1,6 +1,7 @@ -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Ticket.Services; -namespace Modmail.NET.Utils; +namespace Modmail.NET.Common.Utils; public static class UtilAttachment { @@ -13,13 +14,13 @@ public static string GetUri(Guid id) { } public static string GetLocalPath(TicketMessageAttachment attachment) { - var uri = new Uri(Const.AttachmentDownloadDirectory, UriKind.RelativeOrAbsolute); + var uri = new Uri(TicketAttachmentDownloadService.AttachmentDownloadDirectory, UriKind.RelativeOrAbsolute); return uri + "/" + attachment.Id + Path.GetExtension(attachment.FileName); } public static string[] GetAllFiles() { - return !Directory.Exists(Const.AttachmentDownloadDirectory) + return !Directory.Exists(TicketAttachmentDownloadService.AttachmentDownloadDirectory) ? [] - : Directory.GetFiles(Const.AttachmentDownloadDirectory); + : Directory.GetFiles(TicketAttachmentDownloadService.AttachmentDownloadDirectory); } } \ No newline at end of file diff --git a/src/Modmail.NET/Utils/UtilCache.cs b/src/Modmail.NET/Common/Utils/UtilCache.cs similarity index 96% rename from src/Modmail.NET/Utils/UtilCache.cs rename to src/Modmail.NET/Common/Utils/UtilCache.cs index 59a31321..034afed3 100644 --- a/src/Modmail.NET/Utils/UtilCache.cs +++ b/src/Modmail.NET/Common/Utils/UtilCache.cs @@ -1,7 +1,7 @@ using System.Text; using System.Text.Json; -namespace Modmail.NET.Utils; +namespace Modmail.NET.Common.Utils; public static class UtilCache { diff --git a/src/Modmail.NET/Utils/UtilChannelTopic.cs b/src/Modmail.NET/Common/Utils/UtilChannelTopic.cs similarity index 94% rename from src/Modmail.NET/Utils/UtilChannelTopic.cs rename to src/Modmail.NET/Common/Utils/UtilChannelTopic.cs index 058432d7..64d6b596 100644 --- a/src/Modmail.NET/Utils/UtilChannelTopic.cs +++ b/src/Modmail.NET/Common/Utils/UtilChannelTopic.cs @@ -1,6 +1,6 @@ using Serilog; -namespace Modmail.NET.Utils; +namespace Modmail.NET.Common.Utils; public static class UtilChannelTopic diff --git a/src/Modmail.NET/Utils/UtilDate.cs b/src/Modmail.NET/Common/Utils/UtilDate.cs similarity index 72% rename from src/Modmail.NET/Utils/UtilDate.cs rename to src/Modmail.NET/Common/Utils/UtilDate.cs index 39484540..4aff16bb 100644 --- a/src/Modmail.NET/Utils/UtilDate.cs +++ b/src/Modmail.NET/Common/Utils/UtilDate.cs @@ -1,4 +1,4 @@ -namespace Modmail.NET.Utils; +namespace Modmail.NET.Common.Utils; public static class UtilDate { diff --git a/src/Modmail.NET/Utils/UtilHash.cs b/src/Modmail.NET/Common/Utils/UtilHash.cs similarity index 95% rename from src/Modmail.NET/Utils/UtilHash.cs rename to src/Modmail.NET/Common/Utils/UtilHash.cs index 9a821d11..1ce1fae1 100644 --- a/src/Modmail.NET/Utils/UtilHash.cs +++ b/src/Modmail.NET/Common/Utils/UtilHash.cs @@ -1,7 +1,7 @@ using System.Security.Cryptography; using System.Text; -namespace Modmail.NET.Utils; +namespace Modmail.NET.Common.Utils; public static class UtilHash { diff --git a/src/Modmail.NET/Utils/UtilInteraction.cs b/src/Modmail.NET/Common/Utils/UtilInteraction.cs similarity index 87% rename from src/Modmail.NET/Utils/UtilInteraction.cs rename to src/Modmail.NET/Common/Utils/UtilInteraction.cs index b03c4312..c82bd739 100644 --- a/src/Modmail.NET/Utils/UtilInteraction.cs +++ b/src/Modmail.NET/Common/Utils/UtilInteraction.cs @@ -1,6 +1,6 @@ -using Modmail.NET.Exceptions; +using Modmail.NET.Common.Exceptions; -namespace Modmail.NET.Utils; +namespace Modmail.NET.Common.Utils; public static class UtilInteraction { diff --git a/src/Modmail.NET/Utils/UtilMention.cs b/src/Modmail.NET/Common/Utils/UtilMention.cs similarity index 77% rename from src/Modmail.NET/Utils/UtilMention.cs rename to src/Modmail.NET/Common/Utils/UtilMention.cs index 2ed3a9fb..5e30ec38 100644 --- a/src/Modmail.NET/Utils/UtilMention.cs +++ b/src/Modmail.NET/Common/Utils/UtilMention.cs @@ -1,7 +1,7 @@ using System.Text; -using Modmail.NET.Models; +using Modmail.NET.Features.Permission.Models; -namespace Modmail.NET.Utils; +namespace Modmail.NET.Common.Utils; public static class UtilMention { diff --git a/src/Modmail.NET/Utils/UtilPermission.cs b/src/Modmail.NET/Common/Utils/UtilPermission.cs similarity index 96% rename from src/Modmail.NET/Utils/UtilPermission.cs rename to src/Modmail.NET/Common/Utils/UtilPermission.cs index 39e1ce98..f962fd81 100644 --- a/src/Modmail.NET/Utils/UtilPermission.cs +++ b/src/Modmail.NET/Common/Utils/UtilPermission.cs @@ -1,7 +1,8 @@ using DSharpPlus.Entities; -using Modmail.NET.Models; +using Modmail.NET.Features.Permission.Models; +using Modmail.NET.Features.Teams.Static; -namespace Modmail.NET.Utils; +namespace Modmail.NET.Common.Utils; public static class UtilPermission { diff --git a/src/Modmail.NET/Utils/UtilReadable.cs b/src/Modmail.NET/Common/Utils/UtilReadable.cs similarity index 98% rename from src/Modmail.NET/Utils/UtilReadable.cs rename to src/Modmail.NET/Common/Utils/UtilReadable.cs index 705572ec..950e613d 100644 --- a/src/Modmail.NET/Utils/UtilReadable.cs +++ b/src/Modmail.NET/Common/Utils/UtilReadable.cs @@ -1,4 +1,4 @@ -namespace Modmail.NET.Utils; +namespace Modmail.NET.Common.Utils; public static class UtilReadable { diff --git a/src/Modmail.NET/Utils/UtilVersion.cs b/src/Modmail.NET/Common/Utils/UtilVersion.cs similarity index 95% rename from src/Modmail.NET/Utils/UtilVersion.cs rename to src/Modmail.NET/Common/Utils/UtilVersion.cs index 3e461c5d..58eae1f3 100644 --- a/src/Modmail.NET/Utils/UtilVersion.cs +++ b/src/Modmail.NET/Common/Utils/UtilVersion.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -namespace Modmail.NET.Utils; +namespace Modmail.NET.Common.Utils; public static class UtilVersion { diff --git a/src/Modmail.NET/Database/Abstract/IEntity.cs b/src/Modmail.NET/Database/Abstract/IEntity.cs new file mode 100644 index 00000000..86f11027 --- /dev/null +++ b/src/Modmail.NET/Database/Abstract/IEntity.cs @@ -0,0 +1,3 @@ +namespace Modmail.NET.Database.Abstract; + +public interface IEntity { } \ No newline at end of file diff --git a/src/Modmail.NET/Abstract/IGuidId.cs b/src/Modmail.NET/Database/Abstract/IGuidId.cs similarity index 59% rename from src/Modmail.NET/Abstract/IGuidId.cs rename to src/Modmail.NET/Database/Abstract/IGuidId.cs index 92f871f7..b15a8199 100644 --- a/src/Modmail.NET/Abstract/IGuidId.cs +++ b/src/Modmail.NET/Database/Abstract/IGuidId.cs @@ -1,4 +1,4 @@ -namespace Modmail.NET.Abstract; +namespace Modmail.NET.Database.Abstract; public interface IGuidId { diff --git a/src/Modmail.NET/Abstract/IHasRegisterDate.cs b/src/Modmail.NET/Database/Abstract/IHasRegisterDate.cs similarity index 65% rename from src/Modmail.NET/Abstract/IHasRegisterDate.cs rename to src/Modmail.NET/Database/Abstract/IHasRegisterDate.cs index 7ed360d0..0d593d89 100644 --- a/src/Modmail.NET/Abstract/IHasRegisterDate.cs +++ b/src/Modmail.NET/Database/Abstract/IHasRegisterDate.cs @@ -1,4 +1,4 @@ -namespace Modmail.NET.Abstract; +namespace Modmail.NET.Database.Abstract; public interface IHasRegisterDate { diff --git a/src/Modmail.NET/Abstract/IHasUpdateDate.cs b/src/Modmail.NET/Database/Abstract/IHasUpdateDate.cs similarity index 64% rename from src/Modmail.NET/Abstract/IHasUpdateDate.cs rename to src/Modmail.NET/Database/Abstract/IHasUpdateDate.cs index 298e0bba..b32fc1d8 100644 --- a/src/Modmail.NET/Abstract/IHasUpdateDate.cs +++ b/src/Modmail.NET/Database/Abstract/IHasUpdateDate.cs @@ -1,4 +1,4 @@ -namespace Modmail.NET.Abstract; +namespace Modmail.NET.Database.Abstract; public interface IHasUpdateDate { diff --git a/src/Modmail.NET/Database/Configuration/DiscordUserInfoConfiguration.cs b/src/Modmail.NET/Database/Configuration/DiscordUserInfoConfiguration.cs index 9c761c80..f8dc2d14 100644 --- a/src/Modmail.NET/Database/Configuration/DiscordUserInfoConfiguration.cs +++ b/src/Modmail.NET/Database/Configuration/DiscordUserInfoConfiguration.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; namespace Modmail.NET.Database.Configuration; diff --git a/src/Modmail.NET/Database/Configuration/GuildOptionConfiguration.cs b/src/Modmail.NET/Database/Configuration/GuildOptionConfiguration.cs index 99da2377..58dc4c80 100644 --- a/src/Modmail.NET/Database/Configuration/GuildOptionConfiguration.cs +++ b/src/Modmail.NET/Database/Configuration/GuildOptionConfiguration.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; namespace Modmail.NET.Database.Configuration; diff --git a/src/Modmail.NET/Database/Configuration/GuildTeamConfiguration.cs b/src/Modmail.NET/Database/Configuration/GuildTeamConfiguration.cs index 16b1a88b..50c68ba3 100644 --- a/src/Modmail.NET/Database/Configuration/GuildTeamConfiguration.cs +++ b/src/Modmail.NET/Database/Configuration/GuildTeamConfiguration.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; namespace Modmail.NET.Database.Configuration; diff --git a/src/Modmail.NET/Database/Configuration/GuildTeamMemberConfiguration.cs b/src/Modmail.NET/Database/Configuration/GuildTeamMemberConfiguration.cs index fd342f14..4bd7b252 100644 --- a/src/Modmail.NET/Database/Configuration/GuildTeamMemberConfiguration.cs +++ b/src/Modmail.NET/Database/Configuration/GuildTeamMemberConfiguration.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; namespace Modmail.NET.Database.Configuration; diff --git a/src/Modmail.NET/Database/Configuration/TicketBlacklistConfiguration.cs b/src/Modmail.NET/Database/Configuration/TicketBlacklistConfiguration.cs index ea233ce6..8bc1bb52 100644 --- a/src/Modmail.NET/Database/Configuration/TicketBlacklistConfiguration.cs +++ b/src/Modmail.NET/Database/Configuration/TicketBlacklistConfiguration.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; namespace Modmail.NET.Database.Configuration; diff --git a/src/Modmail.NET/Database/Configuration/TicketConfiguration.cs b/src/Modmail.NET/Database/Configuration/TicketConfiguration.cs index c5502ec2..56f75009 100644 --- a/src/Modmail.NET/Database/Configuration/TicketConfiguration.cs +++ b/src/Modmail.NET/Database/Configuration/TicketConfiguration.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; namespace Modmail.NET.Database.Configuration; diff --git a/src/Modmail.NET/Database/Configuration/TicketMessageAttachmentConfiguration.cs b/src/Modmail.NET/Database/Configuration/TicketMessageAttachmentConfiguration.cs index 0930183b..2297eadd 100644 --- a/src/Modmail.NET/Database/Configuration/TicketMessageAttachmentConfiguration.cs +++ b/src/Modmail.NET/Database/Configuration/TicketMessageAttachmentConfiguration.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; namespace Modmail.NET.Database.Configuration; diff --git a/src/Modmail.NET/Database/Configuration/TicketMessageConfiguration.cs b/src/Modmail.NET/Database/Configuration/TicketMessageConfiguration.cs index eb98a13d..57874700 100644 --- a/src/Modmail.NET/Database/Configuration/TicketMessageConfiguration.cs +++ b/src/Modmail.NET/Database/Configuration/TicketMessageConfiguration.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; namespace Modmail.NET.Database.Configuration; diff --git a/src/Modmail.NET/Database/Configuration/TicketNoteConfiguration.cs b/src/Modmail.NET/Database/Configuration/TicketNoteConfiguration.cs index 92e4c0fd..b71b3ade 100644 --- a/src/Modmail.NET/Database/Configuration/TicketNoteConfiguration.cs +++ b/src/Modmail.NET/Database/Configuration/TicketNoteConfiguration.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; namespace Modmail.NET.Database.Configuration; diff --git a/src/Modmail.NET/Database/Configuration/TicketTypeConfiguration.cs b/src/Modmail.NET/Database/Configuration/TicketTypeConfiguration.cs index fdaf3148..3bb7daa9 100644 --- a/src/Modmail.NET/Database/Configuration/TicketTypeConfiguration.cs +++ b/src/Modmail.NET/Database/Configuration/TicketTypeConfiguration.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; namespace Modmail.NET.Database.Configuration; diff --git a/src/Modmail.NET/Entities/DiscordUserInfo.cs b/src/Modmail.NET/Database/Entities/DiscordUserInfo.cs similarity index 91% rename from src/Modmail.NET/Entities/DiscordUserInfo.cs rename to src/Modmail.NET/Database/Entities/DiscordUserInfo.cs index 2cc17481..ef3eca10 100644 --- a/src/Modmail.NET/Entities/DiscordUserInfo.cs +++ b/src/Modmail.NET/Database/Entities/DiscordUserInfo.cs @@ -1,9 +1,10 @@ using System.ComponentModel.DataAnnotations; using DSharpPlus.Entities; -using Modmail.NET.Abstract; -using Modmail.NET.Extensions; +using Modmail.NET.Common.Extensions; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Abstract; -namespace Modmail.NET.Entities; +namespace Modmail.NET.Database.Entities; public class DiscordUserInfo : IHasRegisterDate, IHasUpdateDate, diff --git a/src/Modmail.NET/Entities/GuildOption.cs b/src/Modmail.NET/Database/Entities/GuildOption.cs similarity index 73% rename from src/Modmail.NET/Entities/GuildOption.cs rename to src/Modmail.NET/Database/Entities/GuildOption.cs index a6bdf8bc..5f1800b1 100644 --- a/src/Modmail.NET/Entities/GuildOption.cs +++ b/src/Modmail.NET/Database/Entities/GuildOption.cs @@ -1,7 +1,11 @@ using System.ComponentModel.DataAnnotations; -using Modmail.NET.Abstract; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Abstract; +using Modmail.NET.Features.Metric.Static; +using Modmail.NET.Features.Permission.Static; +using Modmail.NET.Features.Ticket.Static; -namespace Modmail.NET.Entities; +namespace Modmail.NET.Database.Entities; public class GuildOption : IHasRegisterDate, IHasUpdateDate, @@ -24,7 +28,7 @@ public class GuildOption : IHasRegisterDate, public required ulong CategoryId { get; set; } public bool IsEnabled { get; set; } = true; - [Range(Const.TicketTimeoutMinAllowedHours, Const.TicketTimeoutMaxAllowedHours)] + [Range(TicketConstants.TicketTimeoutMinAllowedHours, TicketConstants.TicketTimeoutMaxAllowedHours)] public long TicketTimeoutHours { get; set; } = -1; public bool TakeFeedbackAfterClosing { get; set; } @@ -32,11 +36,11 @@ public class GuildOption : IHasRegisterDate, public bool PublicTranscripts { get; set; } public bool SendTranscriptLinkToUser { get; set; } - [Range(-1, Const.TicketDataDeleteWaitDaysMax)] + [Range(-1, TicketConstants.TicketDataDeleteWaitDaysMax)] public int TicketDataDeleteWaitDays { get; set; } = -1; - [Range(-1, Const.StatisticsCalculateDaysMax)] - public int StatisticsCalculateDays { get; set; } = Const.DefaultStatisticsCalculateDays; + [Range(-1, MetricConstants.StatisticsCalculateDaysMax)] + public int StatisticsCalculateDays { get; set; } = MetricConstants.DefaultStatisticsCalculateDays; public TeamPermissionLevel ManageTicketMinAccessLevel { get; set; } = TeamPermissionLevel.Moderator; public TeamPermissionLevel ManageTeamsMinAccessLevel { get; set; } = TeamPermissionLevel.Admin; diff --git a/src/Modmail.NET/Entities/GuildTeam.cs b/src/Modmail.NET/Database/Entities/GuildTeam.cs similarity index 82% rename from src/Modmail.NET/Entities/GuildTeam.cs rename to src/Modmail.NET/Database/Entities/GuildTeam.cs index 1081e672..5d8ce59f 100644 --- a/src/Modmail.NET/Entities/GuildTeam.cs +++ b/src/Modmail.NET/Database/Entities/GuildTeam.cs @@ -1,7 +1,9 @@ using System.ComponentModel.DataAnnotations; -using Modmail.NET.Abstract; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Abstract; +using Modmail.NET.Features.Permission.Static; -namespace Modmail.NET.Entities; +namespace Modmail.NET.Database.Entities; public class GuildTeam : IHasRegisterDate, IHasUpdateDate, diff --git a/src/Modmail.NET/Entities/GuildTeamMember.cs b/src/Modmail.NET/Database/Entities/GuildTeamMember.cs similarity index 76% rename from src/Modmail.NET/Entities/GuildTeamMember.cs rename to src/Modmail.NET/Database/Entities/GuildTeamMember.cs index febac2e6..f13b9f37 100644 --- a/src/Modmail.NET/Entities/GuildTeamMember.cs +++ b/src/Modmail.NET/Database/Entities/GuildTeamMember.cs @@ -1,6 +1,7 @@ -using Modmail.NET.Abstract; +using Modmail.NET.Database.Abstract; +using Modmail.NET.Features.Teams.Static; -namespace Modmail.NET.Entities; +namespace Modmail.NET.Database.Entities; public class GuildTeamMember : IHasRegisterDate, IEntity, diff --git a/src/Modmail.NET/Entities/Statistic.cs b/src/Modmail.NET/Database/Entities/Statistic.cs similarity index 92% rename from src/Modmail.NET/Entities/Statistic.cs rename to src/Modmail.NET/Database/Entities/Statistic.cs index 49bcb4e5..79d35fa1 100644 --- a/src/Modmail.NET/Entities/Statistic.cs +++ b/src/Modmail.NET/Database/Entities/Statistic.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; -using Modmail.NET.Abstract; +using Modmail.NET.Database.Abstract; -namespace Modmail.NET.Entities; +namespace Modmail.NET.Database.Entities; public class Statistic : IHasRegisterDate, IEntity, diff --git a/src/Modmail.NET/Entities/Ticket.cs b/src/Modmail.NET/Database/Entities/Ticket.cs similarity index 89% rename from src/Modmail.NET/Entities/Ticket.cs rename to src/Modmail.NET/Database/Entities/Ticket.cs index d5fabfa6..29068e33 100644 --- a/src/Modmail.NET/Entities/Ticket.cs +++ b/src/Modmail.NET/Database/Entities/Ticket.cs @@ -1,9 +1,11 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using Modmail.NET.Abstract; -using Modmail.NET.Utils; +using Modmail.NET.Common.Static; +using Modmail.NET.Common.Utils; +using Modmail.NET.Database.Abstract; +using Modmail.NET.Features.Ticket.Static; -namespace Modmail.NET.Entities; +namespace Modmail.NET.Database.Entities; public class Ticket : IHasRegisterDate, IEntity, diff --git a/src/Modmail.NET/Entities/TicketBlacklist.cs b/src/Modmail.NET/Database/Entities/TicketBlacklist.cs similarity index 79% rename from src/Modmail.NET/Entities/TicketBlacklist.cs rename to src/Modmail.NET/Database/Entities/TicketBlacklist.cs index 9c3760f6..9fd17c6e 100644 --- a/src/Modmail.NET/Entities/TicketBlacklist.cs +++ b/src/Modmail.NET/Database/Entities/TicketBlacklist.cs @@ -1,7 +1,8 @@ using System.ComponentModel.DataAnnotations; -using Modmail.NET.Abstract; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Abstract; -namespace Modmail.NET.Entities; +namespace Modmail.NET.Database.Entities; public class TicketBlacklist : IHasRegisterDate, IEntity, diff --git a/src/Modmail.NET/Entities/TicketMessage.cs b/src/Modmail.NET/Database/Entities/TicketMessage.cs similarity index 90% rename from src/Modmail.NET/Entities/TicketMessage.cs rename to src/Modmail.NET/Database/Entities/TicketMessage.cs index a112b8f6..cbf6e2cf 100644 --- a/src/Modmail.NET/Entities/TicketMessage.cs +++ b/src/Modmail.NET/Database/Entities/TicketMessage.cs @@ -1,10 +1,11 @@ using System.ComponentModel.DataAnnotations; using DSharpPlus.Entities; -using Modmail.NET.Abstract; -using Modmail.NET.Exceptions; -using Modmail.NET.Utils; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Static; +using Modmail.NET.Common.Utils; +using Modmail.NET.Database.Abstract; -namespace Modmail.NET.Entities; +namespace Modmail.NET.Database.Entities; public class TicketMessage : IHasRegisterDate, IEntity diff --git a/src/Modmail.NET/Entities/TicketMessageAttachment.cs b/src/Modmail.NET/Database/Entities/TicketMessageAttachment.cs similarity index 91% rename from src/Modmail.NET/Entities/TicketMessageAttachment.cs rename to src/Modmail.NET/Database/Entities/TicketMessageAttachment.cs index f72e42dd..17abc2f7 100644 --- a/src/Modmail.NET/Entities/TicketMessageAttachment.cs +++ b/src/Modmail.NET/Database/Entities/TicketMessageAttachment.cs @@ -1,8 +1,9 @@ using System.ComponentModel.DataAnnotations; using DSharpPlus.Entities; -using Modmail.NET.Abstract; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Abstract; -namespace Modmail.NET.Entities; +namespace Modmail.NET.Database.Entities; public class TicketMessageAttachment : IEntity, IGuidId diff --git a/src/Modmail.NET/Entities/TicketNote.cs b/src/Modmail.NET/Database/Entities/TicketNote.cs similarity index 79% rename from src/Modmail.NET/Entities/TicketNote.cs rename to src/Modmail.NET/Database/Entities/TicketNote.cs index 2932fb40..5f40390f 100644 --- a/src/Modmail.NET/Entities/TicketNote.cs +++ b/src/Modmail.NET/Database/Entities/TicketNote.cs @@ -1,7 +1,8 @@ using System.ComponentModel.DataAnnotations; -using Modmail.NET.Abstract; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Abstract; -namespace Modmail.NET.Entities; +namespace Modmail.NET.Database.Entities; public class TicketNote : IHasRegisterDate, IEntity, diff --git a/src/Modmail.NET/Entities/TicketType.cs b/src/Modmail.NET/Database/Entities/TicketType.cs similarity index 89% rename from src/Modmail.NET/Entities/TicketType.cs rename to src/Modmail.NET/Database/Entities/TicketType.cs index ffef8c97..426ea45e 100644 --- a/src/Modmail.NET/Entities/TicketType.cs +++ b/src/Modmail.NET/Database/Entities/TicketType.cs @@ -1,7 +1,8 @@ using System.ComponentModel.DataAnnotations; -using Modmail.NET.Abstract; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Abstract; -namespace Modmail.NET.Entities; +namespace Modmail.NET.Database.Entities; public class TicketType : IHasRegisterDate, IHasUpdateDate, diff --git a/src/Modmail.NET/Migrations/20240827120247_Initial.Designer.cs b/src/Modmail.NET/Database/Migrations/20240827120247_Initial.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240827120247_Initial.Designer.cs rename to src/Modmail.NET/Database/Migrations/20240827120247_Initial.Designer.cs diff --git a/src/Modmail.NET/Migrations/20240827120247_Initial.cs b/src/Modmail.NET/Database/Migrations/20240827120247_Initial.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240827120247_Initial.cs rename to src/Modmail.NET/Database/Migrations/20240827120247_Initial.cs diff --git a/src/Modmail.NET/Migrations/20240827121750_Ticket_AssignedUser.Designer.cs b/src/Modmail.NET/Database/Migrations/20240827121750_Ticket_AssignedUser.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240827121750_Ticket_AssignedUser.Designer.cs rename to src/Modmail.NET/Database/Migrations/20240827121750_Ticket_AssignedUser.Designer.cs diff --git a/src/Modmail.NET/Migrations/20240827121750_Ticket_AssignedUser.cs b/src/Modmail.NET/Database/Migrations/20240827121750_Ticket_AssignedUser.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240827121750_Ticket_AssignedUser.cs rename to src/Modmail.NET/Database/Migrations/20240827121750_Ticket_AssignedUser.cs diff --git a/src/Modmail.NET/Migrations/20240827153553_GuildOption_Removed_GreetingAndClosingMessage.Designer.cs b/src/Modmail.NET/Database/Migrations/20240827153553_GuildOption_Removed_GreetingAndClosingMessage.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240827153553_GuildOption_Removed_GreetingAndClosingMessage.Designer.cs rename to src/Modmail.NET/Database/Migrations/20240827153553_GuildOption_Removed_GreetingAndClosingMessage.Designer.cs diff --git a/src/Modmail.NET/Migrations/20240827153553_GuildOption_Removed_GreetingAndClosingMessage.cs b/src/Modmail.NET/Database/Migrations/20240827153553_GuildOption_Removed_GreetingAndClosingMessage.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240827153553_GuildOption_Removed_GreetingAndClosingMessage.cs rename to src/Modmail.NET/Database/Migrations/20240827153553_GuildOption_Removed_GreetingAndClosingMessage.cs diff --git a/src/Modmail.NET/Migrations/20240828104105_RemovedUnusedFK.Designer.cs b/src/Modmail.NET/Database/Migrations/20240828104105_RemovedUnusedFK.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240828104105_RemovedUnusedFK.Designer.cs rename to src/Modmail.NET/Database/Migrations/20240828104105_RemovedUnusedFK.Designer.cs diff --git a/src/Modmail.NET/Migrations/20240828104105_RemovedUnusedFK.cs b/src/Modmail.NET/Database/Migrations/20240828104105_RemovedUnusedFK.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240828104105_RemovedUnusedFK.cs rename to src/Modmail.NET/Database/Migrations/20240828104105_RemovedUnusedFK.cs diff --git a/src/Modmail.NET/Migrations/20240830090726_GuildTeamMember_Removed_UpdateDateUtc.Designer.cs b/src/Modmail.NET/Database/Migrations/20240830090726_GuildTeamMember_Removed_UpdateDateUtc.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240830090726_GuildTeamMember_Removed_UpdateDateUtc.Designer.cs rename to src/Modmail.NET/Database/Migrations/20240830090726_GuildTeamMember_Removed_UpdateDateUtc.Designer.cs diff --git a/src/Modmail.NET/Migrations/20240830090726_GuildTeamMember_Removed_UpdateDateUtc.cs b/src/Modmail.NET/Database/Migrations/20240830090726_GuildTeamMember_Removed_UpdateDateUtc.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240830090726_GuildTeamMember_Removed_UpdateDateUtc.cs rename to src/Modmail.NET/Database/Migrations/20240830090726_GuildTeamMember_Removed_UpdateDateUtc.cs diff --git a/src/Modmail.NET/Migrations/20240901191906_TicketType_Added_InEnabled.Designer.cs b/src/Modmail.NET/Database/Migrations/20240901191906_TicketType_Added_InEnabled.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240901191906_TicketType_Added_InEnabled.Designer.cs rename to src/Modmail.NET/Database/Migrations/20240901191906_TicketType_Added_InEnabled.Designer.cs diff --git a/src/Modmail.NET/Migrations/20240901191906_TicketType_Added_InEnabled.cs b/src/Modmail.NET/Database/Migrations/20240901191906_TicketType_Added_InEnabled.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240901191906_TicketType_Added_InEnabled.cs rename to src/Modmail.NET/Database/Migrations/20240901191906_TicketType_Added_InEnabled.cs diff --git a/src/Modmail.NET/Migrations/20240907082958_TicketType_Updated_EmbedIsNullable.Designer.cs b/src/Modmail.NET/Database/Migrations/20240907082958_TicketType_Updated_EmbedIsNullable.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240907082958_TicketType_Updated_EmbedIsNullable.Designer.cs rename to src/Modmail.NET/Database/Migrations/20240907082958_TicketType_Updated_EmbedIsNullable.Designer.cs diff --git a/src/Modmail.NET/Migrations/20240907082958_TicketType_Updated_EmbedIsNullable.cs b/src/Modmail.NET/Database/Migrations/20240907082958_TicketType_Updated_EmbedIsNullable.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240907082958_TicketType_Updated_EmbedIsNullable.cs rename to src/Modmail.NET/Database/Migrations/20240907082958_TicketType_Updated_EmbedIsNullable.cs diff --git a/src/Modmail.NET/Migrations/20240907090419_TicketType_Update_DescriptionIsRequired.Designer.cs b/src/Modmail.NET/Database/Migrations/20240907090419_TicketType_Update_DescriptionIsRequired.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240907090419_TicketType_Update_DescriptionIsRequired.Designer.cs rename to src/Modmail.NET/Database/Migrations/20240907090419_TicketType_Update_DescriptionIsRequired.Designer.cs diff --git a/src/Modmail.NET/Migrations/20240907090419_TicketType_Update_DescriptionIsRequired.cs b/src/Modmail.NET/Database/Migrations/20240907090419_TicketType_Update_DescriptionIsRequired.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240907090419_TicketType_Update_DescriptionIsRequired.cs rename to src/Modmail.NET/Database/Migrations/20240907090419_TicketType_Update_DescriptionIsRequired.cs diff --git a/src/Modmail.NET/Migrations/20240907091824_GuildOption_AddedCol_IsEnableDiscordChannelLogging.Designer.cs b/src/Modmail.NET/Database/Migrations/20240907091824_GuildOption_AddedCol_IsEnableDiscordChannelLogging.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240907091824_GuildOption_AddedCol_IsEnableDiscordChannelLogging.Designer.cs rename to src/Modmail.NET/Database/Migrations/20240907091824_GuildOption_AddedCol_IsEnableDiscordChannelLogging.Designer.cs diff --git a/src/Modmail.NET/Migrations/20240907091824_GuildOption_AddedCol_IsEnableDiscordChannelLogging.cs b/src/Modmail.NET/Database/Migrations/20240907091824_GuildOption_AddedCol_IsEnableDiscordChannelLogging.cs similarity index 100% rename from src/Modmail.NET/Migrations/20240907091824_GuildOption_AddedCol_IsEnableDiscordChannelLogging.cs rename to src/Modmail.NET/Database/Migrations/20240907091824_GuildOption_AddedCol_IsEnableDiscordChannelLogging.cs diff --git a/src/Modmail.NET/Migrations/20250317224506_GuildOption_AddedColumn_AlwaysAnonymous.Designer.cs b/src/Modmail.NET/Database/Migrations/20250317224506_GuildOption_AddedColumn_AlwaysAnonymous.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250317224506_GuildOption_AddedColumn_AlwaysAnonymous.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250317224506_GuildOption_AddedColumn_AlwaysAnonymous.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250317224506_GuildOption_AddedColumn_AlwaysAnonymous.cs b/src/Modmail.NET/Database/Migrations/20250317224506_GuildOption_AddedColumn_AlwaysAnonymous.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250317224506_GuildOption_AddedColumn_AlwaysAnonymous.cs rename to src/Modmail.NET/Database/Migrations/20250317224506_GuildOption_AddedColumn_AlwaysAnonymous.cs diff --git a/src/Modmail.NET/Migrations/20250317230425_GuildOption_ColumnAdded_SlashCommandDisableColumns.Designer.cs b/src/Modmail.NET/Database/Migrations/20250317230425_GuildOption_ColumnAdded_SlashCommandDisableColumns.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250317230425_GuildOption_ColumnAdded_SlashCommandDisableColumns.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250317230425_GuildOption_ColumnAdded_SlashCommandDisableColumns.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250317230425_GuildOption_ColumnAdded_SlashCommandDisableColumns.cs b/src/Modmail.NET/Database/Migrations/20250317230425_GuildOption_ColumnAdded_SlashCommandDisableColumns.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250317230425_GuildOption_ColumnAdded_SlashCommandDisableColumns.cs rename to src/Modmail.NET/Database/Migrations/20250317230425_GuildOption_ColumnAdded_SlashCommandDisableColumns.cs diff --git a/src/Modmail.NET/Migrations/20250319000242_GuildOption_ColAdded_AllowUsersToCloseTickets.Designer.cs b/src/Modmail.NET/Database/Migrations/20250319000242_GuildOption_ColAdded_AllowUsersToCloseTickets.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250319000242_GuildOption_ColAdded_AllowUsersToCloseTickets.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250319000242_GuildOption_ColAdded_AllowUsersToCloseTickets.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250319000242_GuildOption_ColAdded_AllowUsersToCloseTickets.cs b/src/Modmail.NET/Database/Migrations/20250319000242_GuildOption_ColAdded_AllowUsersToCloseTickets.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250319000242_GuildOption_ColAdded_AllowUsersToCloseTickets.cs rename to src/Modmail.NET/Database/Migrations/20250319000242_GuildOption_ColAdded_AllowUsersToCloseTickets.cs diff --git a/src/Modmail.NET/Migrations/20250319013409_GuildOption_AddedAvarageResponseTime.Designer.cs b/src/Modmail.NET/Database/Migrations/20250319013409_GuildOption_AddedAvarageResponseTime.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250319013409_GuildOption_AddedAvarageResponseTime.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250319013409_GuildOption_AddedAvarageResponseTime.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250319013409_GuildOption_AddedAvarageResponseTime.cs b/src/Modmail.NET/Database/Migrations/20250319013409_GuildOption_AddedAvarageResponseTime.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250319013409_GuildOption_AddedAvarageResponseTime.cs rename to src/Modmail.NET/Database/Migrations/20250319013409_GuildOption_AddedAvarageResponseTime.cs diff --git a/src/Modmail.NET/Migrations/20250319030317_GuildOption_AddedCols_AvgData.Designer.cs b/src/Modmail.NET/Database/Migrations/20250319030317_GuildOption_AddedCols_AvgData.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250319030317_GuildOption_AddedCols_AvgData.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250319030317_GuildOption_AddedCols_AvgData.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250319030317_GuildOption_AddedCols_AvgData.cs b/src/Modmail.NET/Database/Migrations/20250319030317_GuildOption_AddedCols_AvgData.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250319030317_GuildOption_AddedCols_AvgData.cs rename to src/Modmail.NET/Database/Migrations/20250319030317_GuildOption_AddedCols_AvgData.cs diff --git a/src/Modmail.NET/Migrations/20250321163931_TicketMessageAttachment_ByteContent_Removed.Designer.cs b/src/Modmail.NET/Database/Migrations/20250321163931_TicketMessageAttachment_ByteContent_Removed.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250321163931_TicketMessageAttachment_ByteContent_Removed.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250321163931_TicketMessageAttachment_ByteContent_Removed.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250321163931_TicketMessageAttachment_ByteContent_Removed.cs b/src/Modmail.NET/Database/Migrations/20250321163931_TicketMessageAttachment_ByteContent_Removed.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250321163931_TicketMessageAttachment_ByteContent_Removed.cs rename to src/Modmail.NET/Database/Migrations/20250321163931_TicketMessageAttachment_ByteContent_Removed.cs diff --git a/src/Modmail.NET/Migrations/20250321164005_GuildOption_IconUrl_Nullable_BugFix.Designer.cs b/src/Modmail.NET/Database/Migrations/20250321164005_GuildOption_IconUrl_Nullable_BugFix.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250321164005_GuildOption_IconUrl_Nullable_BugFix.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250321164005_GuildOption_IconUrl_Nullable_BugFix.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250321164005_GuildOption_IconUrl_Nullable_BugFix.cs b/src/Modmail.NET/Database/Migrations/20250321164005_GuildOption_IconUrl_Nullable_BugFix.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250321164005_GuildOption_IconUrl_Nullable_BugFix.cs rename to src/Modmail.NET/Database/Migrations/20250321164005_GuildOption_IconUrl_Nullable_BugFix.cs diff --git a/src/Modmail.NET/Migrations/20250322125230_GuildOption_RemovedSomeColumns.Designer.cs b/src/Modmail.NET/Database/Migrations/20250322125230_GuildOption_RemovedSomeColumns.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250322125230_GuildOption_RemovedSomeColumns.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250322125230_GuildOption_RemovedSomeColumns.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250322125230_GuildOption_RemovedSomeColumns.cs b/src/Modmail.NET/Database/Migrations/20250322125230_GuildOption_RemovedSomeColumns.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250322125230_GuildOption_RemovedSomeColumns.cs rename to src/Modmail.NET/Database/Migrations/20250322125230_GuildOption_RemovedSomeColumns.cs diff --git a/src/Modmail.NET/Migrations/20250322184829_GuildOption_AddedCol_PermissionLevel.Designer.cs b/src/Modmail.NET/Database/Migrations/20250322184829_GuildOption_AddedCol_PermissionLevel.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250322184829_GuildOption_AddedCol_PermissionLevel.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250322184829_GuildOption_AddedCol_PermissionLevel.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250322184829_GuildOption_AddedCol_PermissionLevel.cs b/src/Modmail.NET/Database/Migrations/20250322184829_GuildOption_AddedCol_PermissionLevel.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250322184829_GuildOption_AddedCol_PermissionLevel.cs rename to src/Modmail.NET/Database/Migrations/20250322184829_GuildOption_AddedCol_PermissionLevel.cs diff --git a/src/Modmail.NET/Migrations/20250322225844_TicketMessageSentByModColAdded.Designer.cs b/src/Modmail.NET/Database/Migrations/20250322225844_TicketMessageSentByModColAdded.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250322225844_TicketMessageSentByModColAdded.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250322225844_TicketMessageSentByModColAdded.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250322225844_TicketMessageSentByModColAdded.cs b/src/Modmail.NET/Database/Migrations/20250322225844_TicketMessageSentByModColAdded.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250322225844_TicketMessageSentByModColAdded.cs rename to src/Modmail.NET/Database/Migrations/20250322225844_TicketMessageSentByModColAdded.cs diff --git a/src/Modmail.NET/Migrations/20250323013715_StatisticTableAdded.Designer.cs b/src/Modmail.NET/Database/Migrations/20250323013715_StatisticTableAdded.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250323013715_StatisticTableAdded.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250323013715_StatisticTableAdded.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250323013715_StatisticTableAdded.cs b/src/Modmail.NET/Database/Migrations/20250323013715_StatisticTableAdded.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250323013715_StatisticTableAdded.cs rename to src/Modmail.NET/Database/Migrations/20250323013715_StatisticTableAdded.cs diff --git a/src/Modmail.NET/Migrations/20250323173554_GuildTeamAddedColAllowAccessToWebPanel.Designer.cs b/src/Modmail.NET/Database/Migrations/20250323173554_GuildTeamAddedColAllowAccessToWebPanel.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250323173554_GuildTeamAddedColAllowAccessToWebPanel.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250323173554_GuildTeamAddedColAllowAccessToWebPanel.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250323173554_GuildTeamAddedColAllowAccessToWebPanel.cs b/src/Modmail.NET/Database/Migrations/20250323173554_GuildTeamAddedColAllowAccessToWebPanel.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250323173554_GuildTeamAddedColAllowAccessToWebPanel.cs rename to src/Modmail.NET/Database/Migrations/20250323173554_GuildTeamAddedColAllowAccessToWebPanel.cs diff --git a/src/Modmail.NET/Migrations/20250323192315_GuildOption_RemovedFeatureOptions.Designer.cs b/src/Modmail.NET/Database/Migrations/20250323192315_GuildOption_RemovedFeatureOptions.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250323192315_GuildOption_RemovedFeatureOptions.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250323192315_GuildOption_RemovedFeatureOptions.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250323192315_GuildOption_RemovedFeatureOptions.cs b/src/Modmail.NET/Database/Migrations/20250323192315_GuildOption_RemovedFeatureOptions.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250323192315_GuildOption_RemovedFeatureOptions.cs rename to src/Modmail.NET/Database/Migrations/20250323192315_GuildOption_RemovedFeatureOptions.cs diff --git a/src/Modmail.NET/Migrations/20250323192910_GuildOptionRemoveOptionPermAccessLevel.Designer.cs b/src/Modmail.NET/Database/Migrations/20250323192910_GuildOptionRemoveOptionPermAccessLevel.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250323192910_GuildOptionRemoveOptionPermAccessLevel.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250323192910_GuildOptionRemoveOptionPermAccessLevel.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250323192910_GuildOptionRemoveOptionPermAccessLevel.cs b/src/Modmail.NET/Database/Migrations/20250323192910_GuildOptionRemoveOptionPermAccessLevel.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250323192910_GuildOptionRemoveOptionPermAccessLevel.cs rename to src/Modmail.NET/Database/Migrations/20250323192910_GuildOptionRemoveOptionPermAccessLevel.cs diff --git a/src/Modmail.NET/Migrations/20250326132054_StatisticUpdatedColNames.Designer.cs b/src/Modmail.NET/Database/Migrations/20250326132054_StatisticUpdatedColNames.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250326132054_StatisticUpdatedColNames.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250326132054_StatisticUpdatedColNames.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250326132054_StatisticUpdatedColNames.cs b/src/Modmail.NET/Database/Migrations/20250326132054_StatisticUpdatedColNames.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250326132054_StatisticUpdatedColNames.cs rename to src/Modmail.NET/Database/Migrations/20250326132054_StatisticUpdatedColNames.cs diff --git a/src/Modmail.NET/Migrations/20250326235413_StatisticRenameResolvedTickets.Designer.cs b/src/Modmail.NET/Database/Migrations/20250326235413_StatisticRenameResolvedTickets.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250326235413_StatisticRenameResolvedTickets.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250326235413_StatisticRenameResolvedTickets.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250326235413_StatisticRenameResolvedTickets.cs b/src/Modmail.NET/Database/Migrations/20250326235413_StatisticRenameResolvedTickets.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250326235413_StatisticRenameResolvedTickets.cs rename to src/Modmail.NET/Database/Migrations/20250326235413_StatisticRenameResolvedTickets.cs diff --git a/src/Modmail.NET/Migrations/20250329182313_GuildOptionRemovedShowConfirmationOnCloseCol.Designer.cs b/src/Modmail.NET/Database/Migrations/20250329182313_GuildOptionRemovedShowConfirmationOnCloseCol.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250329182313_GuildOptionRemovedShowConfirmationOnCloseCol.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250329182313_GuildOptionRemovedShowConfirmationOnCloseCol.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250329182313_GuildOptionRemovedShowConfirmationOnCloseCol.cs b/src/Modmail.NET/Database/Migrations/20250329182313_GuildOptionRemovedShowConfirmationOnCloseCol.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250329182313_GuildOptionRemovedShowConfirmationOnCloseCol.cs rename to src/Modmail.NET/Database/Migrations/20250329182313_GuildOptionRemovedShowConfirmationOnCloseCol.cs diff --git a/src/Modmail.NET/Migrations/20250329204247_GuildOptionPublicTranscripts.Designer.cs b/src/Modmail.NET/Database/Migrations/20250329204247_GuildOptionPublicTranscripts.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250329204247_GuildOptionPublicTranscripts.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250329204247_GuildOptionPublicTranscripts.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250329204247_GuildOptionPublicTranscripts.cs b/src/Modmail.NET/Database/Migrations/20250329204247_GuildOptionPublicTranscripts.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250329204247_GuildOptionPublicTranscripts.cs rename to src/Modmail.NET/Database/Migrations/20250329204247_GuildOptionPublicTranscripts.cs diff --git a/src/Modmail.NET/Migrations/20250329213018_TicketMessageLengthExtendedToNoLimit.Designer.cs b/src/Modmail.NET/Database/Migrations/20250329213018_TicketMessageLengthExtendedToNoLimit.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250329213018_TicketMessageLengthExtendedToNoLimit.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250329213018_TicketMessageLengthExtendedToNoLimit.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250329213018_TicketMessageLengthExtendedToNoLimit.cs b/src/Modmail.NET/Database/Migrations/20250329213018_TicketMessageLengthExtendedToNoLimit.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250329213018_TicketMessageLengthExtendedToNoLimit.cs rename to src/Modmail.NET/Database/Migrations/20250329213018_TicketMessageLengthExtendedToNoLimit.cs diff --git a/src/Modmail.NET/Migrations/20250329223813_GuildOptionSendTranscriptLinkToUserColAdded.Designer.cs b/src/Modmail.NET/Database/Migrations/20250329223813_GuildOptionSendTranscriptLinkToUserColAdded.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250329223813_GuildOptionSendTranscriptLinkToUserColAdded.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250329223813_GuildOptionSendTranscriptLinkToUserColAdded.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250329223813_GuildOptionSendTranscriptLinkToUserColAdded.cs b/src/Modmail.NET/Database/Migrations/20250329223813_GuildOptionSendTranscriptLinkToUserColAdded.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250329223813_GuildOptionSendTranscriptLinkToUserColAdded.cs rename to src/Modmail.NET/Database/Migrations/20250329223813_GuildOptionSendTranscriptLinkToUserColAdded.cs diff --git a/src/Modmail.NET/Migrations/20250406154808_TicketMessageAddedColumnBotMessageId.Designer.cs b/src/Modmail.NET/Database/Migrations/20250406154808_TicketMessageAddedColumnBotMessageId.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250406154808_TicketMessageAddedColumnBotMessageId.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250406154808_TicketMessageAddedColumnBotMessageId.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250406154808_TicketMessageAddedColumnBotMessageId.cs b/src/Modmail.NET/Database/Migrations/20250406154808_TicketMessageAddedColumnBotMessageId.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250406154808_TicketMessageAddedColumnBotMessageId.cs rename to src/Modmail.NET/Database/Migrations/20250406154808_TicketMessageAddedColumnBotMessageId.cs diff --git a/src/Modmail.NET/Migrations/20250406163216_GuildOptionAddedColTicketAutoDeleteWaitDays.Designer.cs b/src/Modmail.NET/Database/Migrations/20250406163216_GuildOptionAddedColTicketAutoDeleteWaitDays.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250406163216_GuildOptionAddedColTicketAutoDeleteWaitDays.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250406163216_GuildOptionAddedColTicketAutoDeleteWaitDays.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250406163216_GuildOptionAddedColTicketAutoDeleteWaitDays.cs b/src/Modmail.NET/Database/Migrations/20250406163216_GuildOptionAddedColTicketAutoDeleteWaitDays.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250406163216_GuildOptionAddedColTicketAutoDeleteWaitDays.cs rename to src/Modmail.NET/Database/Migrations/20250406163216_GuildOptionAddedColTicketAutoDeleteWaitDays.cs diff --git a/src/Modmail.NET/Migrations/20250406180016_Constraints.Designer.cs b/src/Modmail.NET/Database/Migrations/20250406180016_Constraints.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250406180016_Constraints.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250406180016_Constraints.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250406180016_Constraints.cs b/src/Modmail.NET/Database/Migrations/20250406180016_Constraints.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250406180016_Constraints.cs rename to src/Modmail.NET/Database/Migrations/20250406180016_Constraints.cs diff --git a/src/Modmail.NET/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.Designer.cs b/src/Modmail.NET/Database/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.cs b/src/Modmail.NET/Database/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.cs similarity index 88% rename from src/Modmail.NET/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.cs rename to src/Modmail.NET/Database/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.cs index e0206cb4..62ecb0a9 100644 --- a/src/Modmail.NET/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.cs +++ b/src/Modmail.NET/Database/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.cs @@ -1,4 +1,6 @@ using Microsoft.EntityFrameworkCore.Migrations; +using Modmail.NET.Common.Static; +using Modmail.NET.Features.Metric.Static; #nullable disable @@ -15,7 +17,7 @@ protected override void Up(MigrationBuilder migrationBuilder) table: "GuildOptions", type: "int", nullable: false, - defaultValue: Const.DefaultStatisticsCalculateDays); + defaultValue: MetricConstants.DefaultStatisticsCalculateDays); migrationBuilder.AddCheckConstraint( name: "CK_GuildOptions_StatisticsCalculateDays_Range", diff --git a/src/Modmail.NET/Migrations/20250406181350_GuildOptionConstraintFixMinValues.Designer.cs b/src/Modmail.NET/Database/Migrations/20250406181350_GuildOptionConstraintFixMinValues.Designer.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250406181350_GuildOptionConstraintFixMinValues.Designer.cs rename to src/Modmail.NET/Database/Migrations/20250406181350_GuildOptionConstraintFixMinValues.Designer.cs diff --git a/src/Modmail.NET/Migrations/20250406181350_GuildOptionConstraintFixMinValues.cs b/src/Modmail.NET/Database/Migrations/20250406181350_GuildOptionConstraintFixMinValues.cs similarity index 100% rename from src/Modmail.NET/Migrations/20250406181350_GuildOptionConstraintFixMinValues.cs rename to src/Modmail.NET/Database/Migrations/20250406181350_GuildOptionConstraintFixMinValues.cs diff --git a/src/Modmail.NET/Migrations/ModmailDbContextModelSnapshot.cs b/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs similarity index 100% rename from src/Modmail.NET/Migrations/ModmailDbContextModelSnapshot.cs rename to src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs diff --git a/src/Modmail.NET/Database/ModmailDbContext.cs b/src/Modmail.NET/Database/ModmailDbContext.cs index 73e8a36e..08c35e20 100644 --- a/src/Modmail.NET/Database/ModmailDbContext.cs +++ b/src/Modmail.NET/Database/ModmailDbContext.cs @@ -1,5 +1,5 @@ using Microsoft.EntityFrameworkCore; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; using SmartEnum.EFCore; namespace Modmail.NET.Database; diff --git a/src/Modmail.NET/Database/Triggers/IdentityV7Trigger.cs b/src/Modmail.NET/Database/Triggers/IdentityV7Trigger.cs index dedadf2c..b1acda17 100644 --- a/src/Modmail.NET/Database/Triggers/IdentityV7Trigger.cs +++ b/src/Modmail.NET/Database/Triggers/IdentityV7Trigger.cs @@ -1,5 +1,5 @@ using EntityFrameworkCore.Triggered; -using Modmail.NET.Abstract; +using Modmail.NET.Database.Abstract; namespace Modmail.NET.Database.Triggers; diff --git a/src/Modmail.NET/Database/Triggers/RegisterDateTrigger.cs b/src/Modmail.NET/Database/Triggers/RegisterDateTrigger.cs index 54810c01..672a56a8 100644 --- a/src/Modmail.NET/Database/Triggers/RegisterDateTrigger.cs +++ b/src/Modmail.NET/Database/Triggers/RegisterDateTrigger.cs @@ -1,6 +1,6 @@ using EntityFrameworkCore.Triggered; -using Modmail.NET.Abstract; -using Modmail.NET.Utils; +using Modmail.NET.Common.Utils; +using Modmail.NET.Database.Abstract; namespace Modmail.NET.Database.Triggers; diff --git a/src/Modmail.NET/Database/Triggers/UpdateDateTrigger.cs b/src/Modmail.NET/Database/Triggers/UpdateDateTrigger.cs index 881ce8f8..514e1841 100644 --- a/src/Modmail.NET/Database/Triggers/UpdateDateTrigger.cs +++ b/src/Modmail.NET/Database/Triggers/UpdateDateTrigger.cs @@ -1,6 +1,6 @@ using EntityFrameworkCore.Triggered; -using Modmail.NET.Abstract; -using Modmail.NET.Utils; +using Modmail.NET.Common.Utils; +using Modmail.NET.Database.Abstract; namespace Modmail.NET.Database.Triggers; diff --git a/src/Modmail.NET/Exceptions/AnotherServerAlreadySetupException.cs b/src/Modmail.NET/Exceptions/AnotherServerAlreadySetupException.cs deleted file mode 100644 index a4926d41..00000000 --- a/src/Modmail.NET/Exceptions/AnotherServerAlreadySetupException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class AnotherServerAlreadySetupException : BotExceptionBase -{ - public AnotherServerAlreadySetupException() : base(LangProvider.This.GetTranslation(LangKeys.AnotherServerAlreadySetup)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/DbInternalException.cs b/src/Modmail.NET/Exceptions/DbInternalException.cs deleted file mode 100644 index 62df8215..00000000 --- a/src/Modmail.NET/Exceptions/DbInternalException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class DbInternalException : BotExceptionBase -{ - public DbInternalException() : base(LangProvider.This.GetTranslation(LangKeys.DbInternalError)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/InvalidInteractionKeyException.cs b/src/Modmail.NET/Exceptions/InvalidInteractionKeyException.cs deleted file mode 100644 index 4ddf7a58..00000000 --- a/src/Modmail.NET/Exceptions/InvalidInteractionKeyException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class InvalidInteractionKeyException : BotExceptionBase -{ - public InvalidInteractionKeyException() : base(LangProvider.This.GetTranslation(LangKeys.InvalidInteractionKey)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/InvalidMessageIdException.cs b/src/Modmail.NET/Exceptions/InvalidMessageIdException.cs deleted file mode 100644 index d377ed31..00000000 --- a/src/Modmail.NET/Exceptions/InvalidMessageIdException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class InvalidMessageIdException : BotExceptionBase -{ - public InvalidMessageIdException() : base(LangProvider.This.GetTranslation(LangKeys.InvalidMessageId)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/InvalidUserIdException.cs b/src/Modmail.NET/Exceptions/InvalidUserIdException.cs deleted file mode 100644 index 897e6256..00000000 --- a/src/Modmail.NET/Exceptions/InvalidUserIdException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class InvalidUserIdException : BotExceptionBase -{ - public InvalidUserIdException() : base(LangProvider.This.GetTranslation(LangKeys.InvalidUser)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/MainServerAlreadySetupException.cs b/src/Modmail.NET/Exceptions/MainServerAlreadySetupException.cs deleted file mode 100644 index 023fe67c..00000000 --- a/src/Modmail.NET/Exceptions/MainServerAlreadySetupException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class MainServerAlreadySetupException : BotExceptionBase -{ - public MainServerAlreadySetupException() : base(LangProvider.This.GetTranslation(LangKeys.MainServerAlreadySetup)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/MemberAlreadyInTeamException.cs b/src/Modmail.NET/Exceptions/MemberAlreadyInTeamException.cs deleted file mode 100644 index 7ede63f7..00000000 --- a/src/Modmail.NET/Exceptions/MemberAlreadyInTeamException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class MemberAlreadyInTeamException : BotExceptionBase -{ - public MemberAlreadyInTeamException() : base(LangProvider.This.GetTranslation(LangKeys.MemberAlreadyInTeam)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/NotJoinedMainServerException.cs b/src/Modmail.NET/Exceptions/NotJoinedMainServerException.cs deleted file mode 100644 index 0d826072..00000000 --- a/src/Modmail.NET/Exceptions/NotJoinedMainServerException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class NotJoinedMainServerException : BotExceptionBase -{ - public NotJoinedMainServerException() : base(LangProvider.This.GetTranslation(LangKeys.NotJoinedMainServer)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/RoleAlreadyInTeamException.cs b/src/Modmail.NET/Exceptions/RoleAlreadyInTeamException.cs deleted file mode 100644 index ddc8f66f..00000000 --- a/src/Modmail.NET/Exceptions/RoleAlreadyInTeamException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class RoleAlreadyInTeamException : BotExceptionBase -{ - public RoleAlreadyInTeamException() : base(LangProvider.This.GetTranslation(LangKeys.RoleAlreadyInTeam)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/ServerIsNotSetupException.cs b/src/Modmail.NET/Exceptions/ServerIsNotSetupException.cs deleted file mode 100644 index 96d93f2e..00000000 --- a/src/Modmail.NET/Exceptions/ServerIsNotSetupException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class ServerIsNotSetupException : BotExceptionBase -{ - public ServerIsNotSetupException() : base(LangProvider.This.GetTranslation(LangKeys.RoleNotFoundInTeam)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/TeamAlreadyExistsException.cs b/src/Modmail.NET/Exceptions/TeamAlreadyExistsException.cs deleted file mode 100644 index e69075ec..00000000 --- a/src/Modmail.NET/Exceptions/TeamAlreadyExistsException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class TeamAlreadyExistsException : BotExceptionBase -{ - public TeamAlreadyExistsException() : base(LangProvider.This.GetTranslation(LangKeys.TeamAlreadyExists)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/TeamNotExistsException.cs b/src/Modmail.NET/Exceptions/TeamNotExistsException.cs deleted file mode 100644 index 3f2bc57d..00000000 --- a/src/Modmail.NET/Exceptions/TeamNotExistsException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class TeamNotExistsException : BotExceptionBase -{ - public TeamNotExistsException() : base(LangProvider.This.GetTranslation(LangKeys.TeamNotExists)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/TicketAlreadyClosedException.cs b/src/Modmail.NET/Exceptions/TicketAlreadyClosedException.cs deleted file mode 100644 index 4486e014..00000000 --- a/src/Modmail.NET/Exceptions/TicketAlreadyClosedException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class TicketAlreadyClosedException : BotExceptionBase -{ - public TicketAlreadyClosedException() : base(LangProvider.This.GetTranslation(LangKeys.TicketAlreadyClosed)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/TicketMustBeClosedException.cs b/src/Modmail.NET/Exceptions/TicketMustBeClosedException.cs deleted file mode 100644 index d5e9bc17..00000000 --- a/src/Modmail.NET/Exceptions/TicketMustBeClosedException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class TicketMustBeClosedException : BotExceptionBase -{ - public TicketMustBeClosedException() : base(LangProvider.This.GetTranslation(LangKeys.TicketMustBeClosed)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/TicketTimeoutOutOfRangeException.cs b/src/Modmail.NET/Exceptions/TicketTimeoutOutOfRangeException.cs deleted file mode 100644 index fc4b5065..00000000 --- a/src/Modmail.NET/Exceptions/TicketTimeoutOutOfRangeException.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class TicketTimeoutOutOfRangeException : BotExceptionBase -{ - public TicketTimeoutOutOfRangeException() : base(LangKeys.TicketTimeoutValueIsOutOfRange.GetTranslation(), - LangKeys.TicketTimeoutValueMustBeBetweenXAndY.GetTranslation(Const.TicketTimeoutMinAllowedHours, - Const.TicketTimeoutMaxAllowedHours)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/UserAlreadyBlacklistedException.cs b/src/Modmail.NET/Exceptions/UserAlreadyBlacklistedException.cs deleted file mode 100644 index 01e3421f..00000000 --- a/src/Modmail.NET/Exceptions/UserAlreadyBlacklistedException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class UserAlreadyBlacklistedException : BotExceptionBase -{ - public UserAlreadyBlacklistedException() : base(LangProvider.This.GetTranslation(LangKeys.UserAlreadyBlacklisted)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Exceptions/UserIsNotBlacklistedException.cs b/src/Modmail.NET/Exceptions/UserIsNotBlacklistedException.cs deleted file mode 100644 index 10b02605..00000000 --- a/src/Modmail.NET/Exceptions/UserIsNotBlacklistedException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Modmail.NET.Abstract; - -namespace Modmail.NET.Exceptions; - -public class UserIsNotBlacklistedException : BotExceptionBase -{ - public UserIsNotBlacklistedException() : base(LangProvider.This.GetTranslation(LangKeys.UserIsNotBlacklisted)) { } -} \ No newline at end of file diff --git a/src/Modmail.NET/Extensions/ExtException.cs b/src/Modmail.NET/Extensions/ExtException.cs deleted file mode 100644 index 7928783c..00000000 --- a/src/Modmail.NET/Extensions/ExtException.cs +++ /dev/null @@ -1,43 +0,0 @@ -using DSharpPlus.Entities; -using Modmail.NET.Abstract; - -namespace Modmail.NET.Extensions; - -public static class ExtException -{ - public static DiscordWebhookBuilder ToWebhookResponse(this BotExceptionBase exception) { - return Webhooks.Warning(exception.TitleMessage, exception.ContentMessage ?? ""); - } - - public static DiscordEmbedBuilder ToEmbedResponse(this BotExceptionBase exception) { - return Embeds.Warning(exception.TitleMessage, exception.ContentMessage ?? ""); - } - - public static DiscordInteractionResponseBuilder ToInteractionResponse(this BotExceptionBase exception) { - return Interactions.Warning(exception.TitleMessage, exception.ContentMessage ?? ""); - } - - public static DiscordWebhookBuilder ToWebhookResponse(this Exception exception) { - var config = ServiceLocator.GetBotConfig(); - - if (config.Environment == EnvironmentType.Development) return Webhooks.Error(LangProvider.This.GetTranslation(LangKeys.AnExceptionOccurred), exception.Message); - - return Webhooks.Error(LangProvider.This.GetTranslation(LangKeys.AnExceptionOccurred)); - } - - public static DiscordEmbedBuilder ToEmbedResponse(this Exception exception) { - var config = ServiceLocator.GetBotConfig(); - - if (config.Environment == EnvironmentType.Development) return Embeds.Error(LangProvider.This.GetTranslation(LangKeys.AnExceptionOccurred), exception.Message); - - return Embeds.Error(LangProvider.This.GetTranslation(LangKeys.AnExceptionOccurred)); - } - - public static DiscordInteractionResponseBuilder ToInteractionResponse(this Exception exception) { - var config = ServiceLocator.GetBotConfig(); - - if (config.Environment == EnvironmentType.Development) return Interactions.Error(LangProvider.This.GetTranslation(LangKeys.AnExceptionOccurred), exception.Message); - - return Interactions.Error(LangProvider.This.GetTranslation(LangKeys.AnExceptionOccurred)); - } -} \ No newline at end of file diff --git a/src/Modmail.NET/Features/Blacklist/Commands.cs b/src/Modmail.NET/Features/Blacklist/Commands/ProcessAddUserToBlacklistCommand.cs similarity index 51% rename from src/Modmail.NET/Features/Blacklist/Commands.cs rename to src/Modmail.NET/Features/Blacklist/Commands/ProcessAddUserToBlacklistCommand.cs index 8307714a..9ea55165 100644 --- a/src/Modmail.NET/Features/Blacklist/Commands.cs +++ b/src/Modmail.NET/Features/Blacklist/Commands/ProcessAddUserToBlacklistCommand.cs @@ -1,14 +1,11 @@ using MediatR; using Modmail.NET.Abstract; using Modmail.NET.Attributes; -using Modmail.NET.Entities; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Entities; -namespace Modmail.NET.Features.Blacklist; +namespace Modmail.NET.Features.Blacklist.Commands; [PermissionCheck(nameof(AuthPolicy.ManageBlacklist))] public sealed record ProcessAddUserToBlacklistCommand(ulong AuthorizedUserId, ulong UserId, string Reason = null) : IRequest, - IPermissionCheck; - -[PermissionCheck(nameof(AuthPolicy.ManageBlacklist))] -public sealed record ProcessRemoveUserFromBlacklistCommand(ulong AuthorizedUserId, ulong UserId) : IRequest, - IPermissionCheck; \ No newline at end of file + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Blacklist/Commands/ProcessRemoveUserFromBlacklistCommand.cs b/src/Modmail.NET/Features/Blacklist/Commands/ProcessRemoveUserFromBlacklistCommand.cs new file mode 100644 index 00000000..8f681187 --- /dev/null +++ b/src/Modmail.NET/Features/Blacklist/Commands/ProcessRemoveUserFromBlacklistCommand.cs @@ -0,0 +1,11 @@ +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Blacklist.Commands; + +[PermissionCheck(nameof(AuthPolicy.ManageBlacklist))] +public sealed record ProcessRemoveUserFromBlacklistCommand(ulong AuthorizedUserId, ulong UserId) : IRequest, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Blacklist/Handlers/CheckUserBlacklistStatusHandler.cs b/src/Modmail.NET/Features/Blacklist/Handlers/CheckUserBlacklistStatusHandler.cs index d2c3b7ab..b34f857a 100644 --- a/src/Modmail.NET/Features/Blacklist/Handlers/CheckUserBlacklistStatusHandler.cs +++ b/src/Modmail.NET/Features/Blacklist/Handlers/CheckUserBlacklistStatusHandler.cs @@ -1,7 +1,8 @@ using MediatR; using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; +using Modmail.NET.Features.Blacklist.Queries; namespace Modmail.NET.Features.Blacklist.Handlers; diff --git a/src/Modmail.NET/Features/Blacklist/Handlers/GetAllBlacklistHandler.cs b/src/Modmail.NET/Features/Blacklist/Handlers/GetAllBlacklistHandler.cs index 5791859e..5fc3d7ac 100644 --- a/src/Modmail.NET/Features/Blacklist/Handlers/GetAllBlacklistHandler.cs +++ b/src/Modmail.NET/Features/Blacklist/Handlers/GetAllBlacklistHandler.cs @@ -1,8 +1,10 @@ using MediatR; using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Blacklist.Queries; +using Modmail.NET.Language; namespace Modmail.NET.Features.Blacklist.Handlers; diff --git a/src/Modmail.NET/Features/Blacklist/Handlers/GetBlacklistHandler.cs b/src/Modmail.NET/Features/Blacklist/Handlers/GetBlacklistHandler.cs index 0e0f9ec5..2ad07366 100644 --- a/src/Modmail.NET/Features/Blacklist/Handlers/GetBlacklistHandler.cs +++ b/src/Modmail.NET/Features/Blacklist/Handlers/GetBlacklistHandler.cs @@ -1,8 +1,9 @@ using MediatR; using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Blacklist.Queries; namespace Modmail.NET.Features.Blacklist.Handlers; diff --git a/src/Modmail.NET/Features/Blacklist/Handlers/ProcessAddUserToBlacklistHandler.cs b/src/Modmail.NET/Features/Blacklist/Handlers/ProcessAddUserToBlacklistHandler.cs index f857b5bf..ebe56650 100644 --- a/src/Modmail.NET/Features/Blacklist/Handlers/ProcessAddUserToBlacklistHandler.cs +++ b/src/Modmail.NET/Features/Blacklist/Handlers/ProcessAddUserToBlacklistHandler.cs @@ -1,11 +1,17 @@ using MediatR; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Bot; -using Modmail.NET.Features.Ticket; -using Modmail.NET.Features.UserInfo; -using Modmail.NET.Utils; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Blacklist.Commands; +using Modmail.NET.Features.Blacklist.Queries; +using Modmail.NET.Features.Blacklist.Static; +using Modmail.NET.Features.DiscordBot.Queries; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Helpers; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Features.User.Queries; +using Modmail.NET.Language; namespace Modmail.NET.Features.Blacklist.Handlers; @@ -51,13 +57,13 @@ await _sender.Send(new ProcessCloseTicketCommand(activeTicket.Id, var user = await _sender.Send(new GetDiscordUserInfoQuery(request.UserId), cancellationToken); var modUser = await _sender.Send(new GetDiscordUserInfoQuery(request.AuthorizedUserId), cancellationToken); - var embedLog = LogResponses.BlacklistAdded(modUser, user, reason); + var embedLog = LogBotMessages.BlacklistAdded(modUser, user, reason); var logChannel = await _sender.Send(new GetDiscordLogChannelQuery(), cancellationToken); await logChannel.SendMessageAsync(embedLog); var member = await _sender.Send(new GetDiscordMemberQuery(user.Id), cancellationToken); if (member is not null) { - var dmEmbed = UserResponses.YouHaveBeenBlacklisted(reason); + var dmEmbed = BlacklistBotMessages.YouHaveBeenBlacklisted(reason); await member.SendMessageAsync(dmEmbed); } }, cancellationToken); diff --git a/src/Modmail.NET/Features/Blacklist/Handlers/ProcessRemoveUserFromBlacklistHandler.cs b/src/Modmail.NET/Features/Blacklist/Handlers/ProcessRemoveUserFromBlacklistHandler.cs index 6b5dc07d..d3e42a8d 100644 --- a/src/Modmail.NET/Features/Blacklist/Handlers/ProcessRemoveUserFromBlacklistHandler.cs +++ b/src/Modmail.NET/Features/Blacklist/Handlers/ProcessRemoveUserFromBlacklistHandler.cs @@ -1,9 +1,13 @@ using MediatR; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Bot; -using Modmail.NET.Features.UserInfo; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Blacklist.Commands; +using Modmail.NET.Features.Blacklist.Queries; +using Modmail.NET.Features.Blacklist.Static; +using Modmail.NET.Features.DiscordBot.Queries; +using Modmail.NET.Features.Ticket.Helpers; +using Modmail.NET.Features.User.Queries; namespace Modmail.NET.Features.Blacklist.Handlers; @@ -29,11 +33,11 @@ public async Task Handle(ProcessRemoveUserFromBlacklistCommand var member = await _sender.Send(new GetDiscordMemberQuery(request.UserId), cancellationToken); var memberInfo = DiscordUserInfo.FromDiscordMember(member); if (member is not null) { - var dmEmbed = UserResponses.YouHaveBeenRemovedFromBlacklist(modUser); + var dmEmbed = BlacklistBotMessages.YouHaveBeenRemovedFromBlacklist(modUser); await member.SendMessageAsync(dmEmbed); } - var embedLog = LogResponses.BlacklistRemoved(modUser, memberInfo); + var embedLog = LogBotMessages.BlacklistRemoved(modUser, memberInfo); var logChannel = await _sender.Send(new GetDiscordLogChannelQuery(), cancellationToken); await logChannel.SendMessageAsync(embedLog); }, cancellationToken); diff --git a/src/Modmail.NET/Features/Blacklist/Queries.cs b/src/Modmail.NET/Features/Blacklist/Queries.cs deleted file mode 100644 index dbe18871..00000000 --- a/src/Modmail.NET/Features/Blacklist/Queries.cs +++ /dev/null @@ -1,19 +0,0 @@ -using MediatR; -using Modmail.NET.Abstract; -using Modmail.NET.Attributes; -using Modmail.NET.Entities; - -namespace Modmail.NET.Features.Blacklist; - -public sealed record CheckUserBlacklistStatusQuery(ulong DiscordUserId) : IRequest; - -[PermissionCheck(nameof(AuthPolicy.ManageBlacklist))] -public sealed record GetBlacklistQuery( - ulong AuthorizedUserId, - ulong DiscordUserId, - bool AllowNull = false) : IRequest, - IPermissionCheck; - -[PermissionCheck(nameof(AuthPolicy.ManageBlacklist))] -public sealed record GetAllBlacklistQuery(ulong AuthorizedUserId) : IRequest>, - IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Blacklist/Queries/CheckUserBlacklistStatusQuery.cs b/src/Modmail.NET/Features/Blacklist/Queries/CheckUserBlacklistStatusQuery.cs new file mode 100644 index 00000000..7991f387 --- /dev/null +++ b/src/Modmail.NET/Features/Blacklist/Queries/CheckUserBlacklistStatusQuery.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace Modmail.NET.Features.Blacklist.Queries; + +public sealed record CheckUserBlacklistStatusQuery(ulong DiscordUserId) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Blacklist/Queries/GetAllBlacklistQuery.cs b/src/Modmail.NET/Features/Blacklist/Queries/GetAllBlacklistQuery.cs new file mode 100644 index 00000000..e4b553ec --- /dev/null +++ b/src/Modmail.NET/Features/Blacklist/Queries/GetAllBlacklistQuery.cs @@ -0,0 +1,11 @@ +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Blacklist.Queries; + +[PermissionCheck(nameof(AuthPolicy.ManageBlacklist))] +public sealed record GetAllBlacklistQuery(ulong AuthorizedUserId) : IRequest>, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Blacklist/Queries/GetBlacklistQuery.cs b/src/Modmail.NET/Features/Blacklist/Queries/GetBlacklistQuery.cs new file mode 100644 index 00000000..52834795 --- /dev/null +++ b/src/Modmail.NET/Features/Blacklist/Queries/GetBlacklistQuery.cs @@ -0,0 +1,14 @@ +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Blacklist.Queries; + +[PermissionCheck(nameof(AuthPolicy.ManageBlacklist))] +public sealed record GetBlacklistQuery( + ulong AuthorizedUserId, + ulong DiscordUserId, + bool AllowNull = false) : IRequest, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Blacklist/Static/BlacklistBotMessages.cs b/src/Modmail.NET/Features/Blacklist/Static/BlacklistBotMessages.cs new file mode 100644 index 00000000..60c314fa --- /dev/null +++ b/src/Modmail.NET/Features/Blacklist/Static/BlacklistBotMessages.cs @@ -0,0 +1,34 @@ +using DSharpPlus.Entities; +using Modmail.NET.Common.Extensions; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Entities; +using Modmail.NET.Language; + +namespace Modmail.NET.Features.Blacklist.Static; + +public static class BlacklistBotMessages +{ + public static DiscordEmbedBuilder YouHaveBeenBlacklisted(string reason = null) { + var embed = new DiscordEmbedBuilder() + .WithTitle(LangKeys.YouHaveBeenBlacklisted.GetTranslation()) + .WithDescription(LangKeys.YouHaveBeenBlacklistedDescription.GetTranslation()) + .WithGuildInfoFooter() + .WithCustomTimestamp() + .WithColor(ModmailColors.ErrorColor); + + if (!string.IsNullOrEmpty(reason)) embed.AddField(LangKeys.Reason.GetTranslation(), reason); + + return embed; + } + + public static DiscordEmbedBuilder YouHaveBeenRemovedFromBlacklist(DiscordUserInfo user) { + var embed = new DiscordEmbedBuilder() + .WithTitle(LangKeys.YouHaveBeenRemovedFromBlacklist.GetTranslation()) + .WithDescription(LangKeys.YouHaveBeenRemovedFromBlacklistDescription.GetTranslation()) + .WithGuildInfoFooter() + .WithCustomTimestamp() + .WithUserAsAuthor(user) + .WithColor(ModmailColors.SuccessColor); + return embed; + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Features/Bot/Queries.cs b/src/Modmail.NET/Features/Bot/Queries.cs deleted file mode 100644 index 184683f9..00000000 --- a/src/Modmail.NET/Features/Bot/Queries.cs +++ /dev/null @@ -1,13 +0,0 @@ -using DSharpPlus.Entities; -using MediatR; -using Modmail.NET.Attributes; - -namespace Modmail.NET.Features.Bot; - -[CachePolicy("GetMainGuildQuery", 300)] -public sealed record GetDiscordMainGuildQuery : IRequest; - -[CachePolicy("GetDiscordLogChannelQuery", 60)] -public sealed record GetDiscordLogChannelQuery : IRequest; - -public sealed record GetDiscordMemberQuery(ulong UserId) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Events/ComponentInteractionCreatedEvent.cs b/src/Modmail.NET/Features/DiscordBot/Events/ComponentInteractionCreatedEvent.cs similarity index 91% rename from src/Modmail.NET/Events/ComponentInteractionCreatedEvent.cs rename to src/Modmail.NET/Features/DiscordBot/Events/ComponentInteractionCreatedEvent.cs index 2c4c34de..23dee23c 100644 --- a/src/Modmail.NET/Events/ComponentInteractionCreatedEvent.cs +++ b/src/Modmail.NET/Features/DiscordBot/Events/ComponentInteractionCreatedEvent.cs @@ -3,14 +3,15 @@ using DSharpPlus.EventArgs; using MediatR; using Microsoft.Extensions.DependencyInjection; -using Modmail.NET.Abstract; -using Modmail.NET.Aspects; -using Modmail.NET.Features.Ticket; -using Modmail.NET.Features.UserInfo; -using Modmail.NET.Utils; +using Modmail.NET.Common.Aspects; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Helpers; +using Modmail.NET.Features.User.Commands; using Serilog; -namespace Modmail.NET.Events; +namespace Modmail.NET.Features.DiscordBot.Events; public static class ComponentInteractionCreatedEvent { @@ -60,10 +61,10 @@ ComponentInteractionCreatedEventArgs args break; } } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning( ex, - "[{Source}] BotExceptionBase: Error processing component interaction. CustomId: {CustomId}, UserId: {UserId}, ChannelId: {ChannelId}, InteractionId: {InteractionId}, MessageId: {MessageId}", + "[{Source}] ModmailBotException: Error processing component interaction. CustomId: {CustomId}, UserId: {UserId}, ChannelId: {ChannelId}, InteractionId: {InteractionId}, MessageId: {MessageId}", nameof(ComponentInteractionCreatedEvent), args.Interaction?.Data?.CustomId, args.User?.Id, @@ -101,7 +102,7 @@ ulong messageId var starCount = int.Parse(starParam); var ticketId = Guid.Parse(ticketIdParam); - var feedbackModal = Modals.CreateFeedbackModal(starCount, ticketId, messageId); + var feedbackModal = TicketModals.CreateFeedbackModal(starCount, ticketId, messageId); await args.Interaction.CreateResponseAsync( DiscordInteractionResponseType.Modal, @@ -191,7 +192,7 @@ ulong messageId var ticketIdParam = parameters[0]; var ticketId = Guid.Parse(ticketIdParam); - var modal = Modals.CreateCloseTicketWithReasonModal(ticketId); + var modal = TicketModals.CreateCloseTicketWithReasonModal(ticketId); await args.Interaction.CreateResponseAsync( DiscordInteractionResponseType.Modal, modal diff --git a/src/Modmail.NET/Events/ModalSubmittedEvent.cs b/src/Modmail.NET/Features/DiscordBot/Events/ModalSubmittedEvent.cs similarity index 91% rename from src/Modmail.NET/Events/ModalSubmittedEvent.cs rename to src/Modmail.NET/Features/DiscordBot/Events/ModalSubmittedEvent.cs index 5d90c33c..a2e7984e 100644 --- a/src/Modmail.NET/Events/ModalSubmittedEvent.cs +++ b/src/Modmail.NET/Features/DiscordBot/Events/ModalSubmittedEvent.cs @@ -3,14 +3,15 @@ using DSharpPlus.EventArgs; using MediatR; using Microsoft.Extensions.DependencyInjection; -using Modmail.NET.Abstract; -using Modmail.NET.Aspects; -using Modmail.NET.Features.Ticket; -using Modmail.NET.Features.UserInfo; -using Modmail.NET.Utils; +using Modmail.NET.Common.Aspects; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.User.Commands; +using Modmail.NET.Language; using Serilog; -namespace Modmail.NET.Events; +namespace Modmail.NET.Features.DiscordBot.Events; public static class ModalSubmittedEvent { @@ -58,10 +59,10 @@ await args.Interaction.CreateResponseAsync( break; } } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning( ex, - "[{Source}] BotExceptionBase: Error processing modal submission. CustomId: {CustomId}, InteractionId: {InteractionId}", + "[{Source}] ModmailBotException: Error processing modal submission. CustomId: {CustomId}, InteractionId: {InteractionId}", nameof(ModalSubmittedEvent), args.Interaction.Data.CustomId, args.Interaction.Id diff --git a/src/Modmail.NET/Events/OnChannelDeletedEvent.cs b/src/Modmail.NET/Features/DiscordBot/Events/OnChannelDeletedEvent.cs similarity index 88% rename from src/Modmail.NET/Events/OnChannelDeletedEvent.cs rename to src/Modmail.NET/Features/DiscordBot/Events/OnChannelDeletedEvent.cs index 82feb542..d7615bfa 100644 --- a/src/Modmail.NET/Events/OnChannelDeletedEvent.cs +++ b/src/Modmail.NET/Features/DiscordBot/Events/OnChannelDeletedEvent.cs @@ -3,14 +3,16 @@ using DSharpPlus.EventArgs; using MediatR; using Microsoft.Extensions.DependencyInjection; -using Modmail.NET.Abstract; -using Modmail.NET.Aspects; -using Modmail.NET.Features.Ticket; -using Modmail.NET.Features.UserInfo; -using Modmail.NET.Utils; +using Modmail.NET.Common.Aspects; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Features.User.Commands; +using Modmail.NET.Language; using Serilog; -namespace Modmail.NET.Events; +namespace Modmail.NET.Features.DiscordBot.Events; public static class OnChannelDeletedEvent { @@ -87,10 +89,10 @@ await sender.Send(new ProcessCloseTicketCommand( args.Channel.Id ); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning( ex, - "[{Source}] BotExceptionBase: Error processing channel deletion. ChannelId: {ChannelId}", + "[{Source}] ModmailBotException: Error processing channel deletion. ChannelId: {ChannelId}", nameof(OnChannelDeletedEvent), args.Channel.Id ); diff --git a/src/Modmail.NET/Events/OnMessageCreatedEvent.cs b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageCreatedEvent.cs similarity index 82% rename from src/Modmail.NET/Events/OnMessageCreatedEvent.cs rename to src/Modmail.NET/Features/DiscordBot/Events/OnMessageCreatedEvent.cs index c77f0999..af406ee3 100644 --- a/src/Modmail.NET/Events/OnMessageCreatedEvent.cs +++ b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageCreatedEvent.cs @@ -1,9 +1,9 @@ using DSharpPlus; using DSharpPlus.EventArgs; using Microsoft.Extensions.DependencyInjection; -using Modmail.NET.Queues; +using Modmail.NET.Features.Ticket.Services; -namespace Modmail.NET.Events; +namespace Modmail.NET.Features.DiscordBot.Events; public static class OnMessageCreatedEvent { @@ -13,7 +13,7 @@ public static async Task OnMessageCreated(DiscordClient client, MessageCreatedEv if (args.Message.IsTTS) return; var scope = client.ServiceProvider.CreateScope(); - var ticketMessageQueue = scope.ServiceProvider.GetRequiredService(); + var ticketMessageQueue = scope.ServiceProvider.GetRequiredService(); await ticketMessageQueue.Enqueue(args.Author.Id, args); } } \ No newline at end of file diff --git a/src/Modmail.NET/Events/OnMessageDeletedEvent.cs b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageDeletedEvent.cs similarity index 98% rename from src/Modmail.NET/Events/OnMessageDeletedEvent.cs rename to src/Modmail.NET/Features/DiscordBot/Events/OnMessageDeletedEvent.cs index 33bf8977..fa2e25a2 100644 --- a/src/Modmail.NET/Events/OnMessageDeletedEvent.cs +++ b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageDeletedEvent.cs @@ -3,12 +3,12 @@ using DSharpPlus.Exceptions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Modmail.NET.Aspects; +using Modmail.NET.Common.Aspects; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Utils; using Serilog; -namespace Modmail.NET.Events; +namespace Modmail.NET.Features.DiscordBot.Events; public static class OnMessageDeletedEvent { diff --git a/src/Modmail.NET/Events/OnMessageReactionAddedEvent.cs b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageReactionAddedEvent.cs similarity index 96% rename from src/Modmail.NET/Events/OnMessageReactionAddedEvent.cs rename to src/Modmail.NET/Features/DiscordBot/Events/OnMessageReactionAddedEvent.cs index d452fbf1..6b91d951 100644 --- a/src/Modmail.NET/Events/OnMessageReactionAddedEvent.cs +++ b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageReactionAddedEvent.cs @@ -5,13 +5,14 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Modmail.NET.Aspects; +using Modmail.NET.Common.Aspects; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Features.UserInfo; -using Modmail.NET.Utils; +using Modmail.NET.Features.Ticket.Static; +using Modmail.NET.Features.User.Commands; using Serilog; -namespace Modmail.NET.Events; +namespace Modmail.NET.Features.DiscordBot.Events; public class OnMessageReactionAddedEvent { @@ -37,7 +38,7 @@ MessageReactionAddedEventArgs args return; } - if (args.Emoji.Name == Const.ProcessedReactionDiscordEmojiUnicode) { + if (args.Emoji.Name == TicketConstants.ProcessedReactionDiscordEmojiUnicode) { Log.Debug( "[{Source}] Ignoring processed reaction emoji. EmojiName: {EmojiName}", nameof(OnMessageReactionAddedEvent), diff --git a/src/Modmail.NET/Events/OnMessageReactionRemovedEvent.cs b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageReactionRemovedEvent.cs similarity index 96% rename from src/Modmail.NET/Events/OnMessageReactionRemovedEvent.cs rename to src/Modmail.NET/Features/DiscordBot/Events/OnMessageReactionRemovedEvent.cs index 3f938de2..65e07e60 100644 --- a/src/Modmail.NET/Events/OnMessageReactionRemovedEvent.cs +++ b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageReactionRemovedEvent.cs @@ -5,13 +5,14 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Modmail.NET.Aspects; +using Modmail.NET.Common.Aspects; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Features.UserInfo; -using Modmail.NET.Utils; +using Modmail.NET.Features.Ticket.Static; +using Modmail.NET.Features.User.Commands; using Serilog; -namespace Modmail.NET.Events; +namespace Modmail.NET.Features.DiscordBot.Events; public static class OnMessageReactionRemovedEvent { @@ -37,7 +38,7 @@ MessageReactionRemovedEventArgs args return; } - if (args.Emoji.Name == Const.ProcessedReactionDiscordEmojiUnicode) { + if (args.Emoji.Name == TicketConstants.ProcessedReactionDiscordEmojiUnicode) { Log.Debug( "[{Source}] Ignoring processed reaction emoji removed. EmojiName: {EmojiName}", nameof(OnMessageReactionRemovedEvent), diff --git a/src/Modmail.NET/Events/OnMessageUpdatedEvent.cs b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageUpdatedEvent.cs similarity index 97% rename from src/Modmail.NET/Events/OnMessageUpdatedEvent.cs rename to src/Modmail.NET/Features/DiscordBot/Events/OnMessageUpdatedEvent.cs index de77b12e..5fb87fd2 100644 --- a/src/Modmail.NET/Events/OnMessageUpdatedEvent.cs +++ b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageUpdatedEvent.cs @@ -5,13 +5,14 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Modmail.NET.Aspects; +using Modmail.NET.Common.Aspects; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Features.UserInfo; -using Modmail.NET.Utils; +using Modmail.NET.Features.Ticket.Helpers; +using Modmail.NET.Features.User.Commands; using Serilog; -namespace Modmail.NET.Events; +namespace Modmail.NET.Features.DiscordBot.Events; public static class OnMessageUpdatedEvent { @@ -200,7 +201,7 @@ DiscordMessage updatedMessage try { var channel = await client.GetChannelAsync(channelId.Value); var message = await channel.GetMessageAsync(messageId.Value); - var embed = TicketResponses.MessageEdited(updatedMessage); + var embed = TicketBotMessages.Ticket.MessageEdited(updatedMessage); //TODO: Add support for removing attachment files from message on message update event //Currently the library does not support removing single attachments, either we have to remove all of them diff --git a/src/Modmail.NET/Events/UserUpdateEvents.cs b/src/Modmail.NET/Features/DiscordBot/Events/UserUpdateEvents.cs similarity index 94% rename from src/Modmail.NET/Events/UserUpdateEvents.cs rename to src/Modmail.NET/Features/DiscordBot/Events/UserUpdateEvents.cs index 60ce3062..69ac1907 100644 --- a/src/Modmail.NET/Events/UserUpdateEvents.cs +++ b/src/Modmail.NET/Features/DiscordBot/Events/UserUpdateEvents.cs @@ -3,9 +3,9 @@ using DSharpPlus.EventArgs; using MediatR; using Microsoft.Extensions.DependencyInjection; -using Modmail.NET.Features.UserInfo; +using Modmail.NET.Features.User.Commands; -namespace Modmail.NET.Events; +namespace Modmail.NET.Features.DiscordBot.Events; public static class UserUpdateEvents { diff --git a/src/Modmail.NET/Features/Bot/Handlers/GetDiscordLogChannelHandler.cs b/src/Modmail.NET/Features/DiscordBot/Handlers/GetDiscordLogChannelHandler.cs similarity index 86% rename from src/Modmail.NET/Features/Bot/Handlers/GetDiscordLogChannelHandler.cs rename to src/Modmail.NET/Features/DiscordBot/Handlers/GetDiscordLogChannelHandler.cs index 4a4650db..c631fe67 100644 --- a/src/Modmail.NET/Features/Bot/Handlers/GetDiscordLogChannelHandler.cs +++ b/src/Modmail.NET/Features/DiscordBot/Handlers/GetDiscordLogChannelHandler.cs @@ -1,10 +1,12 @@ using DSharpPlus.Entities; using DSharpPlus.Exceptions; using MediatR; -using Modmail.NET.Features.Guild; +using Modmail.NET.Features.DiscordBot.Queries; +using Modmail.NET.Features.Guild.Commands; +using Modmail.NET.Features.Guild.Queries; using Serilog; -namespace Modmail.NET.Features.Bot.Handlers; +namespace Modmail.NET.Features.DiscordBot.Handlers; public class GetDiscordLogChannelHandler : IRequestHandler { diff --git a/src/Modmail.NET/Features/Bot/Handlers/GetDiscordMainGuildHandler.cs b/src/Modmail.NET/Features/DiscordBot/Handlers/GetDiscordMainGuildHandler.cs similarity index 85% rename from src/Modmail.NET/Features/Bot/Handlers/GetDiscordMainGuildHandler.cs rename to src/Modmail.NET/Features/DiscordBot/Handlers/GetDiscordMainGuildHandler.cs index a70da8b7..e7d7a207 100644 --- a/src/Modmail.NET/Features/Bot/Handlers/GetDiscordMainGuildHandler.cs +++ b/src/Modmail.NET/Features/DiscordBot/Handlers/GetDiscordMainGuildHandler.cs @@ -1,14 +1,16 @@ using DSharpPlus.Entities; using MediatR; using Microsoft.Extensions.Options; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Guild; -using Modmail.NET.Features.UserInfo; +using Modmail.NET.Features.DiscordBot.Queries; +using Modmail.NET.Features.Guild.Queries; +using Modmail.NET.Features.User.Commands; +using Modmail.NET.Language; using Serilog; using NotFoundException = DSharpPlus.Exceptions.NotFoundException; -namespace Modmail.NET.Features.Bot.Handlers; +namespace Modmail.NET.Features.DiscordBot.Handlers; public class GetDiscordMainGuildHandler : IRequestHandler { @@ -35,7 +37,7 @@ public async Task Handle(GetDiscordMainGuildQuery request, Cancell } catch (NotFoundException) { Log.Error("Main guild not found: {GuildId}", guildId); - throw new Exceptions.NotFoundException(LangKeys.MainGuild); + throw new Common.Exceptions.NotFoundException(LangKeys.MainGuild); } var guildOption = await _sender.Send(new GetGuildOptionQuery(false), cancellationToken) ?? throw new NullReferenceException(); diff --git a/src/Modmail.NET/Features/Bot/Handlers/GetDiscordMemberHandler.cs b/src/Modmail.NET/Features/DiscordBot/Handlers/GetDiscordMemberHandler.cs similarity index 86% rename from src/Modmail.NET/Features/Bot/Handlers/GetDiscordMemberHandler.cs rename to src/Modmail.NET/Features/DiscordBot/Handlers/GetDiscordMemberHandler.cs index 56ab2b24..d07bd893 100644 --- a/src/Modmail.NET/Features/Bot/Handlers/GetDiscordMemberHandler.cs +++ b/src/Modmail.NET/Features/DiscordBot/Handlers/GetDiscordMemberHandler.cs @@ -1,9 +1,10 @@ using DSharpPlus.Entities; using DSharpPlus.Exceptions; using MediatR; -using Modmail.NET.Features.UserInfo; +using Modmail.NET.Features.DiscordBot.Queries; +using Modmail.NET.Features.User.Commands; -namespace Modmail.NET.Features.Bot.Handlers; +namespace Modmail.NET.Features.DiscordBot.Handlers; public class GetDiscordMemberHandler : IRequestHandler { diff --git a/src/Modmail.NET/Features/DiscordBot/Queries/GetDiscordLogChannelQuery.cs b/src/Modmail.NET/Features/DiscordBot/Queries/GetDiscordLogChannelQuery.cs new file mode 100644 index 00000000..0c93f3c3 --- /dev/null +++ b/src/Modmail.NET/Features/DiscordBot/Queries/GetDiscordLogChannelQuery.cs @@ -0,0 +1,8 @@ +using DSharpPlus.Entities; +using MediatR; +using Modmail.NET.Attributes; + +namespace Modmail.NET.Features.DiscordBot.Queries; + +[CachePolicy("GetDiscordLogChannelQuery", 60)] +public sealed record GetDiscordLogChannelQuery : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/DiscordBot/Queries/GetDiscordMainGuildQuery.cs b/src/Modmail.NET/Features/DiscordBot/Queries/GetDiscordMainGuildQuery.cs new file mode 100644 index 00000000..af555385 --- /dev/null +++ b/src/Modmail.NET/Features/DiscordBot/Queries/GetDiscordMainGuildQuery.cs @@ -0,0 +1,8 @@ +using DSharpPlus.Entities; +using MediatR; +using Modmail.NET.Attributes; + +namespace Modmail.NET.Features.DiscordBot.Queries; + +[CachePolicy("GetMainGuildQuery", 300)] +public sealed record GetDiscordMainGuildQuery : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/DiscordBot/Queries/GetDiscordMemberQuery.cs b/src/Modmail.NET/Features/DiscordBot/Queries/GetDiscordMemberQuery.cs new file mode 100644 index 00000000..897f9092 --- /dev/null +++ b/src/Modmail.NET/Features/DiscordBot/Queries/GetDiscordMemberQuery.cs @@ -0,0 +1,6 @@ +using DSharpPlus.Entities; +using MediatR; + +namespace Modmail.NET.Features.DiscordBot.Queries; + +public sealed record GetDiscordMemberQuery(ulong UserId) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Checks/Attributes/RequireMainServerAttribute.cs b/src/Modmail.NET/Features/DiscordCommands/Checks/Attributes/RequireMainServerAttribute.cs similarity index 62% rename from src/Modmail.NET/Checks/Attributes/RequireMainServerAttribute.cs rename to src/Modmail.NET/Features/DiscordCommands/Checks/Attributes/RequireMainServerAttribute.cs index c385d448..e97d981e 100644 --- a/src/Modmail.NET/Checks/Attributes/RequireMainServerAttribute.cs +++ b/src/Modmail.NET/Features/DiscordCommands/Checks/Attributes/RequireMainServerAttribute.cs @@ -1,5 +1,5 @@ using DSharpPlus.Commands.ContextChecks; -namespace Modmail.NET.Checks.Attributes; +namespace Modmail.NET.Features.DiscordCommands.Checks.Attributes; public class RequireMainServerAttribute : ContextCheckAttribute; \ No newline at end of file diff --git a/src/Modmail.NET/Checks/Attributes/RequirePermissionLevelOrHigherAttribute.cs b/src/Modmail.NET/Features/DiscordCommands/Checks/Attributes/RequirePermissionLevelOrHigherAttribute.cs similarity index 74% rename from src/Modmail.NET/Checks/Attributes/RequirePermissionLevelOrHigherAttribute.cs rename to src/Modmail.NET/Features/DiscordCommands/Checks/Attributes/RequirePermissionLevelOrHigherAttribute.cs index 80355fc8..5dbe222d 100644 --- a/src/Modmail.NET/Checks/Attributes/RequirePermissionLevelOrHigherAttribute.cs +++ b/src/Modmail.NET/Features/DiscordCommands/Checks/Attributes/RequirePermissionLevelOrHigherAttribute.cs @@ -1,6 +1,7 @@ using DSharpPlus.Commands.ContextChecks; +using Modmail.NET.Features.Permission.Static; -namespace Modmail.NET.Checks.Attributes; +namespace Modmail.NET.Features.DiscordCommands.Checks.Attributes; public class RequirePermissionLevelOrHigherAttribute : ContextCheckAttribute { diff --git a/src/Modmail.NET/Checks/Attributes/RequireTicketChannelAttribute.cs b/src/Modmail.NET/Features/DiscordCommands/Checks/Attributes/RequireTicketChannelAttribute.cs similarity index 63% rename from src/Modmail.NET/Checks/Attributes/RequireTicketChannelAttribute.cs rename to src/Modmail.NET/Features/DiscordCommands/Checks/Attributes/RequireTicketChannelAttribute.cs index 583386c0..39d806d5 100644 --- a/src/Modmail.NET/Checks/Attributes/RequireTicketChannelAttribute.cs +++ b/src/Modmail.NET/Features/DiscordCommands/Checks/Attributes/RequireTicketChannelAttribute.cs @@ -1,5 +1,5 @@ using DSharpPlus.Commands.ContextChecks; -namespace Modmail.NET.Checks.Attributes; +namespace Modmail.NET.Features.DiscordCommands.Checks.Attributes; public class RequireTicketChannelAttribute : ContextCheckAttribute; \ No newline at end of file diff --git a/src/Modmail.NET/Checks/Attributes/UpdateUserInformationAttribute.cs b/src/Modmail.NET/Features/DiscordCommands/Checks/Attributes/UpdateUserInformationAttribute.cs similarity index 63% rename from src/Modmail.NET/Checks/Attributes/UpdateUserInformationAttribute.cs rename to src/Modmail.NET/Features/DiscordCommands/Checks/Attributes/UpdateUserInformationAttribute.cs index 64d2c71e..7e45ed1c 100644 --- a/src/Modmail.NET/Checks/Attributes/UpdateUserInformationAttribute.cs +++ b/src/Modmail.NET/Features/DiscordCommands/Checks/Attributes/UpdateUserInformationAttribute.cs @@ -1,5 +1,5 @@ using DSharpPlus.Commands.ContextChecks; -namespace Modmail.NET.Checks.Attributes; +namespace Modmail.NET.Features.DiscordCommands.Checks.Attributes; public class UpdateUserInformationAttribute : ContextCheckAttribute; \ No newline at end of file diff --git a/src/Modmail.NET/Checks/RequireMainServerCheck.cs b/src/Modmail.NET/Features/DiscordCommands/Checks/RequireMainServerCheck.cs similarity index 81% rename from src/Modmail.NET/Checks/RequireMainServerCheck.cs rename to src/Modmail.NET/Features/DiscordCommands/Checks/RequireMainServerCheck.cs index 4ca36646..45d5ddd7 100644 --- a/src/Modmail.NET/Checks/RequireMainServerCheck.cs +++ b/src/Modmail.NET/Features/DiscordCommands/Checks/RequireMainServerCheck.cs @@ -2,9 +2,10 @@ using DSharpPlus.Commands.ContextChecks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Modmail.NET.Checks.Attributes; +using Modmail.NET.Features.DiscordCommands.Checks.Attributes; +using Modmail.NET.Language; -namespace Modmail.NET.Checks; +namespace Modmail.NET.Features.DiscordCommands.Checks; public class RequireMainServerCheck : IContextCheck { diff --git a/src/Modmail.NET/Checks/RequirePermissionLevelOrHigherCheck.cs b/src/Modmail.NET/Features/DiscordCommands/Checks/RequirePermissionLevelOrHigherCheck.cs similarity index 89% rename from src/Modmail.NET/Checks/RequirePermissionLevelOrHigherCheck.cs rename to src/Modmail.NET/Features/DiscordCommands/Checks/RequirePermissionLevelOrHigherCheck.cs index 34413472..bc5aabec 100644 --- a/src/Modmail.NET/Checks/RequirePermissionLevelOrHigherCheck.cs +++ b/src/Modmail.NET/Features/DiscordCommands/Checks/RequirePermissionLevelOrHigherCheck.cs @@ -4,10 +4,11 @@ using MediatR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Modmail.NET.Checks.Attributes; -using Modmail.NET.Features.Permission; +using Modmail.NET.Features.DiscordCommands.Checks.Attributes; +using Modmail.NET.Features.Permission.Queries; +using Modmail.NET.Language; -namespace Modmail.NET.Checks; +namespace Modmail.NET.Features.DiscordCommands.Checks; public class RequirePermissionLevelOrHigherCheck : IContextCheck { diff --git a/src/Modmail.NET/Checks/RequireTicketChannelCheck.cs b/src/Modmail.NET/Features/DiscordCommands/Checks/RequireTicketChannelCheck.cs similarity index 74% rename from src/Modmail.NET/Checks/RequireTicketChannelCheck.cs rename to src/Modmail.NET/Features/DiscordCommands/Checks/RequireTicketChannelCheck.cs index 1757ac4c..785a48b7 100644 --- a/src/Modmail.NET/Checks/RequireTicketChannelCheck.cs +++ b/src/Modmail.NET/Features/DiscordCommands/Checks/RequireTicketChannelCheck.cs @@ -1,9 +1,10 @@ using DSharpPlus.Commands; using DSharpPlus.Commands.ContextChecks; -using Modmail.NET.Checks.Attributes; -using Modmail.NET.Utils; +using Modmail.NET.Common.Utils; +using Modmail.NET.Features.DiscordCommands.Checks.Attributes; +using Modmail.NET.Language; -namespace Modmail.NET.Checks; +namespace Modmail.NET.Features.DiscordCommands.Checks; public class RequireTicketChannelCheck : IContextCheck { diff --git a/src/Modmail.NET/Checks/UpdateUserInformationCheck.cs b/src/Modmail.NET/Features/DiscordCommands/Checks/UpdateUserInformationCheck.cs similarity index 77% rename from src/Modmail.NET/Checks/UpdateUserInformationCheck.cs rename to src/Modmail.NET/Features/DiscordCommands/Checks/UpdateUserInformationCheck.cs index dce1abb0..b142fefa 100644 --- a/src/Modmail.NET/Checks/UpdateUserInformationCheck.cs +++ b/src/Modmail.NET/Features/DiscordCommands/Checks/UpdateUserInformationCheck.cs @@ -2,10 +2,10 @@ using DSharpPlus.Commands.ContextChecks; using MediatR; using Microsoft.Extensions.DependencyInjection; -using Modmail.NET.Checks.Attributes; -using Modmail.NET.Features.UserInfo; +using Modmail.NET.Features.DiscordCommands.Checks.Attributes; +using Modmail.NET.Features.User.Commands; -namespace Modmail.NET.Checks; +namespace Modmail.NET.Features.DiscordCommands.Checks; public class UpdateUserInformationCheck : IContextCheck { diff --git a/src/Modmail.NET/Commands/Slash/BlacklistSlashCommands.cs b/src/Modmail.NET/Features/DiscordCommands/Handlers/BlacklistSlashCommands.cs similarity index 78% rename from src/Modmail.NET/Commands/Slash/BlacklistSlashCommands.cs rename to src/Modmail.NET/Features/DiscordCommands/Handlers/BlacklistSlashCommands.cs index 22b5d51d..642c1ce1 100644 --- a/src/Modmail.NET/Commands/Slash/BlacklistSlashCommands.cs +++ b/src/Modmail.NET/Features/DiscordCommands/Handlers/BlacklistSlashCommands.cs @@ -3,15 +3,19 @@ using DSharpPlus.Commands.Processors.SlashCommands; using DSharpPlus.Entities; using MediatR; -using Modmail.NET.Abstract; -using Modmail.NET.Aspects; -using Modmail.NET.Checks.Attributes; -using Modmail.NET.Extensions; -using Modmail.NET.Features.Blacklist; -using Modmail.NET.Features.UserInfo; +using Modmail.NET.Common.Aspects; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Extensions; +using Modmail.NET.Common.Static; +using Modmail.NET.Features.Blacklist.Commands; +using Modmail.NET.Features.Blacklist.Queries; +using Modmail.NET.Features.DiscordCommands.Checks.Attributes; +using Modmail.NET.Features.Permission.Static; +using Modmail.NET.Features.User.Commands; +using Modmail.NET.Language; using Serilog; -namespace Modmail.NET.Commands.Slash; +namespace Modmail.NET.Features.DiscordCommands.Handlers; [PerformanceLoggerAspect] [Command("blacklist")] @@ -39,13 +43,13 @@ public async Task Add(SlashCommandContext ctx, try { await _sender.Send(new UpdateDiscordUserCommand(user)); await _sender.Send(new ProcessAddUserToBlacklistCommand(ctx.User.Id, user.Id, reason)); - await ctx.EditResponseAsync(Webhooks.Success(LangKeys.UserBlacklisted.GetTranslation())); + await ctx.EditResponseAsync(ModmailWebhooks.Success(LangKeys.UserBlacklisted.GetTranslation())); Log.Information(logMessage, ctx.User.Id, user.Id, reason); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning(ex, logMessage, ctx.User.Id, @@ -78,9 +82,9 @@ DiscordUser user Log.Information(logMessage, ctx.User.Id, user.Id); - await ctx.EditResponseAsync(Webhooks.Success(LangKeys.UserBlacklisted.GetTranslation())); + await ctx.EditResponseAsync(ModmailWebhooks.Success(LangKeys.UserBlacklisted.GetTranslation())); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { await ctx.EditResponseAsync(ex.ToWebhookResponse()); Log.Warning(ex, logMessage, @@ -106,13 +110,13 @@ DiscordUser user await ctx.Interaction.CreateResponseAsync(DiscordInteractionResponseType.DeferredChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral()); try { var isBlocked = await _sender.Send(new CheckUserBlacklistStatusQuery(user.Id)); - await ctx.EditResponseAsync(Webhooks.Info(LangKeys.UserBlacklistStatus.GetTranslation(), - isBlocked - ? LangKeys.UserIsBlacklisted.GetTranslation() - : LangKeys.UserIsNotBlacklisted.GetTranslation())); + await ctx.EditResponseAsync(ModmailWebhooks.Info(LangKeys.UserBlacklistStatus.GetTranslation(), + isBlocked + ? LangKeys.UserIsBlacklisted.GetTranslation() + : LangKeys.UserIsNotBlacklisted.GetTranslation())); Log.Information(logMessage, ctx.User.Id, user.Id); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { await ctx.EditResponseAsync(ex.ToWebhookResponse()); Log.Warning(ex, logMessage, ctx.User.Id, user.Id); } diff --git a/src/Modmail.NET/Commands/ModmailCommands.cs b/src/Modmail.NET/Features/DiscordCommands/Handlers/ModmailCommands.cs similarity index 71% rename from src/Modmail.NET/Commands/ModmailCommands.cs rename to src/Modmail.NET/Features/DiscordCommands/Handlers/ModmailCommands.cs index 161f496d..40e138b5 100644 --- a/src/Modmail.NET/Commands/ModmailCommands.cs +++ b/src/Modmail.NET/Features/DiscordCommands/Handlers/ModmailCommands.cs @@ -3,14 +3,16 @@ using DSharpPlus.Commands.ContextChecks; using DSharpPlus.Entities; using MediatR; -using Modmail.NET.Abstract; -using Modmail.NET.Aspects; -using Modmail.NET.Checks.Attributes; -using Modmail.NET.Extensions; -using Modmail.NET.Features.Guild; +using Modmail.NET.Common.Aspects; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Extensions; +using Modmail.NET.Common.Static; +using Modmail.NET.Features.DiscordCommands.Checks.Attributes; +using Modmail.NET.Features.Guild.Commands; +using Modmail.NET.Language; using Serilog; -namespace Modmail.NET.Commands; +namespace Modmail.NET.Features.DiscordCommands.Handlers; [PerformanceLoggerAspect] [RequireGuild] @@ -33,11 +35,11 @@ public async Task Setup(CommandContext ctx) { try { await _sender.Send(new ProcessGuildSetupCommand(ctx.User.Id, ctx.Guild)); - await ctx.RespondAsync(Embeds.Success(LangKeys.ServerSetupComplete.GetTranslation())); + await ctx.RespondAsync(ModmailEmbeds.Success(LangKeys.ServerSetupComplete.GetTranslation())); Log.Information(logMessage, ctx.User.Id); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { await ctx.RespondAsync(ex.ToEmbedResponse()); Log.Warning(ex, logMessage, diff --git a/src/Modmail.NET/Commands/Slash/TicketSlashCommands.cs b/src/Modmail.NET/Features/DiscordCommands/Handlers/TicketSlashCommands.cs similarity index 79% rename from src/Modmail.NET/Commands/Slash/TicketSlashCommands.cs rename to src/Modmail.NET/Features/DiscordCommands/Handlers/TicketSlashCommands.cs index 3d97ce42..50729123 100644 --- a/src/Modmail.NET/Commands/Slash/TicketSlashCommands.cs +++ b/src/Modmail.NET/Features/DiscordCommands/Handlers/TicketSlashCommands.cs @@ -4,18 +4,22 @@ using DSharpPlus.Commands.Processors.SlashCommands.ArgumentModifiers; using DSharpPlus.Entities; using MediatR; -using Modmail.NET.Abstract; -using Modmail.NET.Aspects; -using Modmail.NET.Checks.Attributes; -using Modmail.NET.Extensions; -using Modmail.NET.Features.Permission; -using Modmail.NET.Features.Ticket; -using Modmail.NET.Features.TicketType; -using Modmail.NET.Providers; -using Modmail.NET.Utils; +using Modmail.NET.Common.Aspects; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Extensions; +using Modmail.NET.Common.Static; +using Modmail.NET.Common.Utils; +using Modmail.NET.Features.DiscordCommands.Checks.Attributes; +using Modmail.NET.Features.DiscordCommands.Helpers; +using Modmail.NET.Features.Permission.Queries; +using Modmail.NET.Features.Permission.Static; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Features.Ticket.Static; +using Modmail.NET.Language; using Serilog; -namespace Modmail.NET.Commands.Slash; +namespace Modmail.NET.Features.DiscordCommands.Handlers; [PerformanceLoggerAspect] [Command("ticket")] @@ -46,16 +50,16 @@ public async Task CloseTicket(SlashCommandContext ctx, var isAnyTeamMember = await _sender.Send(new CheckUserInAnyTeamQuery(ctx.User.Id)); if (!isAnyTeamMember) { await ctx.Interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage, - Interactions.Error(LangKeys.YouDoNotHavePermissionToUseThisCommand.GetTranslation()).AsEphemeral()); + ModmailInteractions.Error(LangKeys.YouDoNotHavePermissionToUseThisCommand.GetTranslation()).AsEphemeral()); return; } } await _sender.Send(new ProcessCloseTicketCommand(ticketId, ctx.User.Id, reason, ctx.Channel)); - await ctx.Interaction.EditOriginalResponseAsync(Webhooks.Success(LangKeys.TicketClosed.GetTranslation())); + await ctx.Interaction.EditOriginalResponseAsync(ModmailWebhooks.Success(LangKeys.TicketClosed.GetTranslation())); Log.Information(logMessage, ctx.User.Id, reason); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { await ctx.Interaction.EditOriginalResponseAsync(ex.ToWebhookResponse()); Log.Warning(ex, logMessage, ctx.User.Id, reason); } @@ -75,10 +79,10 @@ public async Task SetPriority(SlashCommandContext ctx, try { var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(ctx.Channel.Topic); await _sender.Send(new ProcessChangePriorityCommand(ticketId, ctx.User.Id, priority, ctx.Channel)); - await ctx.Interaction.EditOriginalResponseAsync(Webhooks.Success(LangKeys.TicketPriorityChanged.GetTranslation())); + await ctx.Interaction.EditOriginalResponseAsync(ModmailWebhooks.Success(LangKeys.TicketPriorityChanged.GetTranslation())); Log.Information(logMessage, ctx.User.Id, priority); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { await ctx.Interaction.EditOriginalResponseAsync(ex.ToWebhookResponse()); Log.Warning(ex, logMessage, ctx.User.Id, priority); } @@ -100,10 +104,10 @@ string note try { var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(ctx.Channel.Topic); await _sender.Send(new ProcessAddNoteCommand(ticketId, ctx.User.Id, note)); - await ctx.Interaction.EditOriginalResponseAsync(Webhooks.Success(LangKeys.NoteAdded.GetTranslation())); + await ctx.Interaction.EditOriginalResponseAsync(ModmailWebhooks.Success(LangKeys.NoteAdded.GetTranslation())); Log.Information(logMessage, ctx.User.Id, note); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { await ctx.Interaction.EditOriginalResponseAsync(ex.ToWebhookResponse()); Log.Warning(ex, logMessage, ctx.User.Id, note); } @@ -121,10 +125,10 @@ public async Task ToggleAnonymous(SlashCommandContext ctx) { try { var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(ctx.Channel.Topic); await _sender.Send(new ProcessToggleAnonymousCommand(ticketId, ctx.Channel)); - await ctx.Interaction.EditOriginalResponseAsync(Webhooks.Success(LangKeys.TicketAnonymousToggled.GetTranslation())); + await ctx.Interaction.EditOriginalResponseAsync(ModmailWebhooks.Success(LangKeys.TicketAnonymousToggled.GetTranslation())); Log.Information(logMessage, ctx.User.Id); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { await ctx.Interaction.EditOriginalResponseAsync(ex.ToWebhookResponse()); Log.Warning(ex, logMessage, ctx.User.Id); } @@ -145,10 +149,10 @@ public async Task SetType(SlashCommandContext ctx, try { var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(ctx.Channel.Topic); await _sender.Send(new ProcessChangeTicketTypeCommand(ticketId, type, ctx.Channel, UserId: ctx.User.Id)); - await ctx.Interaction.EditOriginalResponseAsync(Webhooks.Success(LangKeys.TicketTypeChanged.GetTranslation())); + await ctx.Interaction.EditOriginalResponseAsync(ModmailWebhooks.Success(LangKeys.TicketTypeChanged.GetTranslation())); Log.Information(logMessage, ctx.User.Id, type); } - catch (BotExceptionBase e) { + catch (ModmailBotException e) { await ctx.Interaction.EditOriginalResponseAsync(e.ToWebhookResponse()); Log.Warning(e, logMessage, ctx.User.Id, type); } @@ -167,12 +171,12 @@ public async Task GetTicketType(SlashCommandContext ctx) { try { var ticketType = await _sender.Send(new GetTicketTypeByChannelIdQuery(ctx.Channel.Id, true)); if (ticketType is null) - await ctx.EditResponseAsync(Webhooks.Info(LangKeys.TicketTypeNotSet.GetTranslation())); + await ctx.EditResponseAsync(ModmailWebhooks.Info(LangKeys.TicketTypeNotSet.GetTranslation())); else - await ctx.EditResponseAsync(Webhooks.Info(LangKeys.TicketType.GetTranslation(), $"`{ticketType.Name}` - {ticketType.Description}")); + await ctx.EditResponseAsync(ModmailWebhooks.Info(LangKeys.TicketType.GetTranslation(), $"`{ticketType.Name}` - {ticketType.Description}")); Log.Information(logMessage); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { await ctx.EditResponseAsync(ex.ToWebhookResponse()); Log.Warning(ex, logMessage); } diff --git a/src/Modmail.NET/Providers/TicketTypeProvider.cs b/src/Modmail.NET/Features/DiscordCommands/Helpers/TicketTypeProvider.cs similarity index 92% rename from src/Modmail.NET/Providers/TicketTypeProvider.cs rename to src/Modmail.NET/Features/DiscordCommands/Helpers/TicketTypeProvider.cs index d8bc900a..d15ebf9d 100644 --- a/src/Modmail.NET/Providers/TicketTypeProvider.cs +++ b/src/Modmail.NET/Features/DiscordCommands/Helpers/TicketTypeProvider.cs @@ -4,9 +4,9 @@ using MediatR; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; -using Modmail.NET.Features.TicketType; +using Modmail.NET.Features.Ticket.Queries; -namespace Modmail.NET.Providers; +namespace Modmail.NET.Features.DiscordCommands.Helpers; public class TicketTypeProvider : IAutoCompleteProvider { diff --git a/src/Modmail.NET/Features/Guild/Commands.cs b/src/Modmail.NET/Features/Guild/Commands.cs deleted file mode 100644 index 634dce75..00000000 --- a/src/Modmail.NET/Features/Guild/Commands.cs +++ /dev/null @@ -1,19 +0,0 @@ -using DSharpPlus.Entities; -using MediatR; -using Modmail.NET.Abstract; -using Modmail.NET.Attributes; -using Modmail.NET.Entities; - -namespace Modmail.NET.Features.Guild; - -[PermissionCheck(nameof(AuthPolicy.Owner))] -public sealed record ClearGuildOptionCommand(ulong AuthorizedUserId) : IRequest, - IPermissionCheck; - -[PermissionCheck(nameof(AuthPolicy.Owner))] -public sealed record ProcessGuildSetupCommand(ulong AuthorizedUserId, DiscordGuild Guild) : IRequest, - IPermissionCheck; - -[PermissionCheck(nameof(AuthPolicy.Owner))] -public sealed record ProcessCreateLogChannelCommand(ulong AuthorizedUserId, DiscordGuild Guild) : IRequest, - IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Guild/Commands/ClearGuildOptionCommand.cs b/src/Modmail.NET/Features/Guild/Commands/ClearGuildOptionCommand.cs new file mode 100644 index 00000000..6308cb82 --- /dev/null +++ b/src/Modmail.NET/Features/Guild/Commands/ClearGuildOptionCommand.cs @@ -0,0 +1,10 @@ +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; + +namespace Modmail.NET.Features.Guild.Commands; + +[PermissionCheck(nameof(AuthPolicy.Owner))] +public sealed record ClearGuildOptionCommand(ulong AuthorizedUserId) : IRequest, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Guild/Commands/ProcessCreateLogChannelCommand.cs b/src/Modmail.NET/Features/Guild/Commands/ProcessCreateLogChannelCommand.cs new file mode 100644 index 00000000..86729f49 --- /dev/null +++ b/src/Modmail.NET/Features/Guild/Commands/ProcessCreateLogChannelCommand.cs @@ -0,0 +1,11 @@ +using DSharpPlus.Entities; +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; + +namespace Modmail.NET.Features.Guild.Commands; + +[PermissionCheck(nameof(AuthPolicy.Owner))] +public sealed record ProcessCreateLogChannelCommand(ulong AuthorizedUserId, DiscordGuild Guild) : IRequest, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Guild/Commands/ProcessGuildSetupCommand.cs b/src/Modmail.NET/Features/Guild/Commands/ProcessGuildSetupCommand.cs new file mode 100644 index 00000000..6341ba90 --- /dev/null +++ b/src/Modmail.NET/Features/Guild/Commands/ProcessGuildSetupCommand.cs @@ -0,0 +1,12 @@ +using DSharpPlus.Entities; +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Guild.Commands; + +[PermissionCheck(nameof(AuthPolicy.Owner))] +public sealed record ProcessGuildSetupCommand(ulong AuthorizedUserId, DiscordGuild Guild) : IRequest, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Guild/Handlers/CheckAnyGuildSetupHandler.cs b/src/Modmail.NET/Features/Guild/Handlers/CheckAnyGuildSetupHandler.cs index 49974d45..2e3b97ba 100644 --- a/src/Modmail.NET/Features/Guild/Handlers/CheckAnyGuildSetupHandler.cs +++ b/src/Modmail.NET/Features/Guild/Handlers/CheckAnyGuildSetupHandler.cs @@ -1,6 +1,7 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Modmail.NET.Database; +using Modmail.NET.Features.Guild.Queries; namespace Modmail.NET.Features.Guild.Handlers; diff --git a/src/Modmail.NET/Features/Guild/Handlers/ClearGuildOptionHandler.cs b/src/Modmail.NET/Features/Guild/Handlers/ClearGuildOptionHandler.cs index e9416ca9..b4fa6065 100644 --- a/src/Modmail.NET/Features/Guild/Handlers/ClearGuildOptionHandler.cs +++ b/src/Modmail.NET/Features/Guild/Handlers/ClearGuildOptionHandler.cs @@ -1,6 +1,7 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Modmail.NET.Database; +using Modmail.NET.Features.Guild.Commands; namespace Modmail.NET.Features.Guild.Handlers; diff --git a/src/Modmail.NET/Features/Guild/Handlers/GetGuildOptionHandler.cs b/src/Modmail.NET/Features/Guild/Handlers/GetGuildOptionHandler.cs index d9533c8d..b0b5ef75 100644 --- a/src/Modmail.NET/Features/Guild/Handlers/GetGuildOptionHandler.cs +++ b/src/Modmail.NET/Features/Guild/Handlers/GetGuildOptionHandler.cs @@ -1,9 +1,10 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Guild.Queries; namespace Modmail.NET.Features.Guild.Handlers; diff --git a/src/Modmail.NET/Features/Guild/Handlers/ProcessCreateLogChannelHandlers.cs b/src/Modmail.NET/Features/Guild/Handlers/ProcessCreateLogChannelHandlers.cs index 382d6071..98f2af8c 100644 --- a/src/Modmail.NET/Features/Guild/Handlers/ProcessCreateLogChannelHandlers.cs +++ b/src/Modmail.NET/Features/Guild/Handlers/ProcessCreateLogChannelHandlers.cs @@ -1,10 +1,16 @@ using DSharpPlus.Entities; using MediatR; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Bot; -using Modmail.NET.Features.Permission; -using Modmail.NET.Utils; +using Modmail.NET.Features.DiscordBot.Queries; +using Modmail.NET.Features.Guild.Commands; +using Modmail.NET.Features.Guild.Queries; +using Modmail.NET.Features.Guild.Static; +using Modmail.NET.Features.Permission.Queries; +using Modmail.NET.Features.Permission.Static; +using Modmail.NET.Features.Teams.Static; +using Modmail.NET.Language; namespace Modmail.NET.Features.Guild.Handlers; @@ -42,10 +48,10 @@ public async Task Handle(ProcessCreateLogChannelCommand request, category = await guild.GetChannelAsync(guildOption.CategoryId); } catch (NotFoundException) { - category = await guild.CreateChannelCategoryAsync(Const.CategoryName, permissionOverwrites); + category = await guild.CreateChannelCategoryAsync(GuildConstants.CategoryName, permissionOverwrites); } - var logChannel = await guild.CreateTextChannelAsync(Const.LogChannelName, category, LangProvider.This.GetTranslation(LangKeys.ModmailLogChannelTopic), permissionOverwrites); + var logChannel = await guild.CreateTextChannelAsync(GuildConstants.LogChannelName, category, LangProvider.This.GetTranslation(LangKeys.ModmailLogChannelTopic), permissionOverwrites); guildOption.LogChannelId = logChannel.Id; guildOption.CategoryId = category.Id; diff --git a/src/Modmail.NET/Features/Guild/Handlers/ProcessGuildSetupHandler.cs b/src/Modmail.NET/Features/Guild/Handlers/ProcessGuildSetupHandler.cs index 5427e420..20dd2b72 100644 --- a/src/Modmail.NET/Features/Guild/Handlers/ProcessGuildSetupHandler.cs +++ b/src/Modmail.NET/Features/Guild/Handlers/ProcessGuildSetupHandler.cs @@ -1,8 +1,10 @@ using MediatR; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; -using Modmail.NET.Utils; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Guild.Commands; +using Modmail.NET.Features.Guild.Queries; namespace Modmail.NET.Features.Guild.Handlers; @@ -32,7 +34,7 @@ public async Task Handle(ProcessGuildSetupCommand request, Cancella TakeFeedbackAfterClosing = false, IconUrl = request.Guild.IconUrl, Name = request.Guild.Name, - BannerUrl = request.Guild.BannerUrl, + BannerUrl = request.Guild.BannerUrl }; await _sender.Send(new ClearGuildOptionCommand(request.AuthorizedUserId), cancellationToken); diff --git a/src/Modmail.NET/Features/Guild/Queries/CheckAnyGuildSetupQuery.cs b/src/Modmail.NET/Features/Guild/Queries/CheckAnyGuildSetupQuery.cs new file mode 100644 index 00000000..044948ba --- /dev/null +++ b/src/Modmail.NET/Features/Guild/Queries/CheckAnyGuildSetupQuery.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace Modmail.NET.Features.Guild.Queries; + +public sealed record CheckAnyGuildSetupQuery : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Guild/Queries.cs b/src/Modmail.NET/Features/Guild/Queries/GetGuildOptionQuery.cs similarity index 50% rename from src/Modmail.NET/Features/Guild/Queries.cs rename to src/Modmail.NET/Features/Guild/Queries/GetGuildOptionQuery.cs index 900bda79..1f3a8bbc 100644 --- a/src/Modmail.NET/Features/Guild/Queries.cs +++ b/src/Modmail.NET/Features/Guild/Queries/GetGuildOptionQuery.cs @@ -1,10 +1,8 @@ using MediatR; using Modmail.NET.Attributes; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; -namespace Modmail.NET.Features.Guild; +namespace Modmail.NET.Features.Guild.Queries; [CachePolicy("GetGuildOptionQuery", 60)] -public sealed record GetGuildOptionQuery(bool AllowNull) : IRequest; - -public sealed record CheckAnyGuildSetupQuery : IRequest; \ No newline at end of file +public sealed record GetGuildOptionQuery(bool AllowNull) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Guild/Static/GuildConstants.cs b/src/Modmail.NET/Features/Guild/Static/GuildConstants.cs new file mode 100644 index 00000000..ddbabd54 --- /dev/null +++ b/src/Modmail.NET/Features/Guild/Static/GuildConstants.cs @@ -0,0 +1,7 @@ +namespace Modmail.NET.Features.Guild.Static; + +public static class GuildConstants +{ + public const string CategoryName = "Modmail"; + public const string LogChannelName = "📄modmail-logs"; +} \ No newline at end of file diff --git a/src/Modmail.NET/Features/Metric/Commands.cs b/src/Modmail.NET/Features/Metric/Commands.cs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Modmail.NET/Features/Metric/Handlers/GetLatestMetricHandler.cs b/src/Modmail.NET/Features/Metric/Handlers/GetLatestMetricHandler.cs index 57863a28..2cf84b34 100644 --- a/src/Modmail.NET/Features/Metric/Handlers/GetLatestMetricHandler.cs +++ b/src/Modmail.NET/Features/Metric/Handlers/GetLatestMetricHandler.cs @@ -1,8 +1,11 @@ using MediatR; using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; -using Modmail.NET.Models.Dto; +using Modmail.NET.Features.Metric.Models; +using Modmail.NET.Features.Metric.Queries; +using Modmail.NET.Features.Teams.Static; +using Modmail.NET.Language; namespace Modmail.NET.Features.Metric.Handlers; diff --git a/src/Modmail.NET/Jobs/StatisticsCalculatorJob.cs b/src/Modmail.NET/Features/Metric/Jobs/StatisticsCalculatorJob.cs similarity index 94% rename from src/Modmail.NET/Jobs/StatisticsCalculatorJob.cs rename to src/Modmail.NET/Features/Metric/Jobs/StatisticsCalculatorJob.cs index cc597495..6bd0cdd1 100644 --- a/src/Modmail.NET/Jobs/StatisticsCalculatorJob.cs +++ b/src/Modmail.NET/Features/Metric/Jobs/StatisticsCalculatorJob.cs @@ -3,14 +3,15 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Modmail.NET.Abstract; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Guild; -using Modmail.NET.Utils; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Guild.Queries; +using Modmail.NET.Features.Metric.Static; using Serilog; -namespace Modmail.NET.Jobs; +namespace Modmail.NET.Features.Metric.Jobs; public class StatisticsCalculatorJob : HangfireRecurringJobBase { @@ -28,7 +29,7 @@ public override async Task Execute() { var sender = scope.ServiceProvider.GetRequiredService(); var guildOption = await sender.Send(new GetGuildOptionQuery(false)); var statDays = guildOption.StatisticsCalculateDays; - if (statDays is < Const.StatisticsCalculateDaysMin or > Const.StatisticsCalculateDaysMax) statDays = Const.DefaultStatisticsCalculateDays; + if (statDays is < MetricConstants.StatisticsCalculateDaysMin or > MetricConstants.StatisticsCalculateDaysMax) statDays = MetricConstants.DefaultStatisticsCalculateDays; var statDate = UtilDate.GetNow().AddDays(-statDays); diff --git a/src/Modmail.NET/Models/Dto/ChartItemDto.cs b/src/Modmail.NET/Features/Metric/Models/ChartItemDto.cs similarity index 65% rename from src/Modmail.NET/Models/Dto/ChartItemDto.cs rename to src/Modmail.NET/Features/Metric/Models/ChartItemDto.cs index fe68a825..49103df3 100644 --- a/src/Modmail.NET/Models/Dto/ChartItemDto.cs +++ b/src/Modmail.NET/Features/Metric/Models/ChartItemDto.cs @@ -1,3 +1,3 @@ -namespace Modmail.NET.Models.Dto; +namespace Modmail.NET.Features.Metric.Models; public sealed record ChartItemDto(TCategory Category, TValue Value); \ No newline at end of file diff --git a/src/Modmail.NET/Models/Dto/MetricDto.cs b/src/Modmail.NET/Features/Metric/Models/MetricDto.cs similarity index 92% rename from src/Modmail.NET/Models/Dto/MetricDto.cs rename to src/Modmail.NET/Features/Metric/Models/MetricDto.cs index 430c31f0..8daadba1 100644 --- a/src/Modmail.NET/Models/Dto/MetricDto.cs +++ b/src/Modmail.NET/Features/Metric/Models/MetricDto.cs @@ -1,6 +1,6 @@ -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; -namespace Modmail.NET.Models.Dto; +namespace Modmail.NET.Features.Metric.Models; public sealed record MetricDto { diff --git a/src/Modmail.NET/Features/Metric/Queries.cs b/src/Modmail.NET/Features/Metric/Queries/GetLatestMetricQuery.cs similarity index 67% rename from src/Modmail.NET/Features/Metric/Queries.cs rename to src/Modmail.NET/Features/Metric/Queries/GetLatestMetricQuery.cs index 5a86064b..c6ba9811 100644 --- a/src/Modmail.NET/Features/Metric/Queries.cs +++ b/src/Modmail.NET/Features/Metric/Queries/GetLatestMetricQuery.cs @@ -1,8 +1,8 @@ using MediatR; using Modmail.NET.Attributes; -using Modmail.NET.Models.Dto; +using Modmail.NET.Features.Metric.Models; -namespace Modmail.NET.Features.Metric; +namespace Modmail.NET.Features.Metric.Queries; [CachePolicy(nameof(GetLatestMetricQuery), 60 * 60, false)] public sealed record GetLatestMetricQuery(bool AllowNull) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Metric/Static/MetricConstants.cs b/src/Modmail.NET/Features/Metric/Static/MetricConstants.cs new file mode 100644 index 00000000..ece49d0c --- /dev/null +++ b/src/Modmail.NET/Features/Metric/Static/MetricConstants.cs @@ -0,0 +1,8 @@ +namespace Modmail.NET.Features.Metric.Static; + +public static class MetricConstants +{ + public const int StatisticsCalculateDaysMin = 30; + public const int StatisticsCalculateDaysMax = 365; + public const int DefaultStatisticsCalculateDays = 90; +} \ No newline at end of file diff --git a/src/Modmail.NET/Features/Permission/Handler/CheckPermissionAccessHandler.cs b/src/Modmail.NET/Features/Permission/Handler/CheckPermissionAccessHandler.cs index 4bf35850..1e596903 100644 --- a/src/Modmail.NET/Features/Permission/Handler/CheckPermissionAccessHandler.cs +++ b/src/Modmail.NET/Features/Permission/Handler/CheckPermissionAccessHandler.cs @@ -1,7 +1,10 @@ using MediatR; using Microsoft.Extensions.Options; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Guild; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Static; +using Modmail.NET.Features.Guild.Queries; +using Modmail.NET.Features.Permission.Queries; +using Modmail.NET.Features.Permission.Static; namespace Modmail.NET.Features.Permission.Handler; diff --git a/src/Modmail.NET/Features/Permission/Handler/CheckRoleInAnyTeamHandler.cs b/src/Modmail.NET/Features/Permission/Handler/CheckRoleInAnyTeamHandler.cs index 0cdaa37e..05db7700 100644 --- a/src/Modmail.NET/Features/Permission/Handler/CheckRoleInAnyTeamHandler.cs +++ b/src/Modmail.NET/Features/Permission/Handler/CheckRoleInAnyTeamHandler.cs @@ -1,6 +1,8 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Modmail.NET.Database; +using Modmail.NET.Features.Permission.Queries; +using Modmail.NET.Features.Teams.Static; namespace Modmail.NET.Features.Permission.Handler; diff --git a/src/Modmail.NET/Features/Permission/Handler/GetPermissionInfoHandler.cs b/src/Modmail.NET/Features/Permission/Handler/GetPermissionInfoHandler.cs index 47799dbf..5369912e 100644 --- a/src/Modmail.NET/Features/Permission/Handler/GetPermissionInfoHandler.cs +++ b/src/Modmail.NET/Features/Permission/Handler/GetPermissionInfoHandler.cs @@ -1,7 +1,8 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Modmail.NET.Database; -using Modmail.NET.Models; +using Modmail.NET.Features.Permission.Models; +using Modmail.NET.Features.Permission.Queries; namespace Modmail.NET.Features.Permission.Handler; diff --git a/src/Modmail.NET/Features/Permission/Handler/GetPermissionInfoOrHigherHandler.cs b/src/Modmail.NET/Features/Permission/Handler/GetPermissionInfoOrHigherHandler.cs index 06c55275..088e0119 100644 --- a/src/Modmail.NET/Features/Permission/Handler/GetPermissionInfoOrHigherHandler.cs +++ b/src/Modmail.NET/Features/Permission/Handler/GetPermissionInfoOrHigherHandler.cs @@ -1,7 +1,8 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Modmail.NET.Database; -using Modmail.NET.Models; +using Modmail.NET.Features.Permission.Models; +using Modmail.NET.Features.Permission.Queries; namespace Modmail.NET.Features.Permission.Handler; diff --git a/src/Modmail.NET/Features/Permission/Handler/GetPermissionLevelHandler.cs b/src/Modmail.NET/Features/Permission/Handler/GetPermissionLevelHandler.cs index 0b2d9884..e55b2584 100644 --- a/src/Modmail.NET/Features/Permission/Handler/GetPermissionLevelHandler.cs +++ b/src/Modmail.NET/Features/Permission/Handler/GetPermissionLevelHandler.cs @@ -2,7 +2,10 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Modmail.NET.Database; -using Modmail.NET.Features.Bot; +using Modmail.NET.Features.DiscordBot.Queries; +using Modmail.NET.Features.Permission.Queries; +using Modmail.NET.Features.Permission.Static; +using Modmail.NET.Features.Teams.Static; namespace Modmail.NET.Features.Permission.Handler; diff --git a/src/Modmail.NET/Models/PermissionInfo.cs b/src/Modmail.NET/Features/Permission/Models/PermissionInfo.cs similarity index 73% rename from src/Modmail.NET/Models/PermissionInfo.cs rename to src/Modmail.NET/Features/Permission/Models/PermissionInfo.cs index 8e5d30ac..11fafd17 100644 --- a/src/Modmail.NET/Models/PermissionInfo.cs +++ b/src/Modmail.NET/Features/Permission/Models/PermissionInfo.cs @@ -1,4 +1,7 @@ -namespace Modmail.NET.Models; +using Modmail.NET.Features.Permission.Static; +using Modmail.NET.Features.Teams.Static; + +namespace Modmail.NET.Features.Permission.Models; public sealed record PermissionInfo(TeamPermissionLevel PermissionLevel, ulong Key, TeamMemberDataType Type, bool PingOnNewTicket, bool PingOnNewMessage) { diff --git a/src/Modmail.NET/Features/Permission/Queries.cs b/src/Modmail.NET/Features/Permission/Queries.cs deleted file mode 100644 index 05145a6b..00000000 --- a/src/Modmail.NET/Features/Permission/Queries.cs +++ /dev/null @@ -1,16 +0,0 @@ -using MediatR; -using Modmail.NET.Models; - -namespace Modmail.NET.Features.Permission; - -public sealed record GetPermissionLevelQuery(ulong UserId, bool IncludeRole = false) : IRequest; - -public sealed record GetPermissionInfoQuery : IRequest; - -public sealed record GetPermissionInfoOrHigherQuery(TeamPermissionLevel LevelOrHigher) : IRequest; - -public sealed record CheckUserInAnyTeamQuery(ulong MemberId) : IRequest; - -public sealed record CheckRoleInAnyTeamQuery(ulong RoleId) : IRequest; - -public sealed record CheckPermissionAccessQuery(ulong UserId, AuthPolicy Policy) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Permission/Queries/CheckPermissionAccessQuery.cs b/src/Modmail.NET/Features/Permission/Queries/CheckPermissionAccessQuery.cs new file mode 100644 index 00000000..0f2bc11e --- /dev/null +++ b/src/Modmail.NET/Features/Permission/Queries/CheckPermissionAccessQuery.cs @@ -0,0 +1,6 @@ +using MediatR; +using Modmail.NET.Common.Static; + +namespace Modmail.NET.Features.Permission.Queries; + +public sealed record CheckPermissionAccessQuery(ulong UserId, AuthPolicy Policy) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Permission/Queries/CheckRoleInAnyTeamQuery.cs b/src/Modmail.NET/Features/Permission/Queries/CheckRoleInAnyTeamQuery.cs new file mode 100644 index 00000000..210eaaa7 --- /dev/null +++ b/src/Modmail.NET/Features/Permission/Queries/CheckRoleInAnyTeamQuery.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace Modmail.NET.Features.Permission.Queries; + +public sealed record CheckRoleInAnyTeamQuery(ulong RoleId) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Permission/Queries/CheckUserInAnyTeamQuery.cs b/src/Modmail.NET/Features/Permission/Queries/CheckUserInAnyTeamQuery.cs new file mode 100644 index 00000000..a934060d --- /dev/null +++ b/src/Modmail.NET/Features/Permission/Queries/CheckUserInAnyTeamQuery.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace Modmail.NET.Features.Permission.Queries; + +public sealed record CheckUserInAnyTeamQuery(ulong MemberId) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Permission/Queries/GetPermissionInfoOrHigherQuery.cs b/src/Modmail.NET/Features/Permission/Queries/GetPermissionInfoOrHigherQuery.cs new file mode 100644 index 00000000..c952cc3f --- /dev/null +++ b/src/Modmail.NET/Features/Permission/Queries/GetPermissionInfoOrHigherQuery.cs @@ -0,0 +1,7 @@ +using MediatR; +using Modmail.NET.Features.Permission.Models; +using Modmail.NET.Features.Permission.Static; + +namespace Modmail.NET.Features.Permission.Queries; + +public sealed record GetPermissionInfoOrHigherQuery(TeamPermissionLevel LevelOrHigher) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Permission/Queries/GetPermissionInfoQuery.cs b/src/Modmail.NET/Features/Permission/Queries/GetPermissionInfoQuery.cs new file mode 100644 index 00000000..1b62b9eb --- /dev/null +++ b/src/Modmail.NET/Features/Permission/Queries/GetPermissionInfoQuery.cs @@ -0,0 +1,6 @@ +using MediatR; +using Modmail.NET.Features.Permission.Models; + +namespace Modmail.NET.Features.Permission.Queries; + +public sealed record GetPermissionInfoQuery : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Permission/Queries/GetPermissionLevelQuery.cs b/src/Modmail.NET/Features/Permission/Queries/GetPermissionLevelQuery.cs new file mode 100644 index 00000000..6f627374 --- /dev/null +++ b/src/Modmail.NET/Features/Permission/Queries/GetPermissionLevelQuery.cs @@ -0,0 +1,6 @@ +using MediatR; +using Modmail.NET.Features.Permission.Static; + +namespace Modmail.NET.Features.Permission.Queries; + +public sealed record GetPermissionLevelQuery(ulong UserId, bool IncludeRole = false) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Static/TeamPermissionLevel.cs b/src/Modmail.NET/Features/Permission/Static/TeamPermissionLevel.cs similarity index 64% rename from src/Modmail.NET/Static/TeamPermissionLevel.cs rename to src/Modmail.NET/Features/Permission/Static/TeamPermissionLevel.cs index 671cb423..429ece38 100644 --- a/src/Modmail.NET/Static/TeamPermissionLevel.cs +++ b/src/Modmail.NET/Features/Permission/Static/TeamPermissionLevel.cs @@ -1,4 +1,4 @@ -namespace Modmail.NET.Static; +namespace Modmail.NET.Features.Permission.Static; public enum TeamPermissionLevel { diff --git a/src/Modmail.NET/Features/Teams/Commands.cs b/src/Modmail.NET/Features/Teams/Commands.cs deleted file mode 100644 index 49686f04..00000000 --- a/src/Modmail.NET/Features/Teams/Commands.cs +++ /dev/null @@ -1,64 +0,0 @@ -using DSharpPlus.Entities; -using MediatR; -using Modmail.NET.Abstract; -using Modmail.NET.Attributes; -using Modmail.NET.Entities; - -namespace Modmail.NET.Features.Teams; - -[PermissionCheck(nameof(AuthPolicy.ManageTeams))] -public sealed record ProcessRenameTeamCommand( - ulong AuthorizedUserId, - Guid Id, - string NewName) : IRequest, - IPermissionCheck; - -[PermissionCheck(nameof(AuthPolicy.ManageTeams))] -public sealed record ProcessRemoveTeamCommand( - ulong AuthorizedUserId, - Guid Id) : IRequest, - IPermissionCheck; - -[PermissionCheck(nameof(AuthPolicy.ManageTeams))] -public sealed record ProcessAddTeamMemberCommand( - ulong AuthorizedUserId, - Guid Id, - ulong MemberId) : IRequest, - IPermissionCheck; - -[PermissionCheck(nameof(AuthPolicy.ManageTeams))] -public sealed record ProcessRemoveTeamMemberCommand( - ulong AuthorizedUserId, - ulong TeamMemberKey, - TeamMemberDataType Type) : IRequest, - IPermissionCheck; - -[PermissionCheck(nameof(AuthPolicy.ManageTeams))] -public sealed record ProcessUpdateTeamCommand( - ulong AuthorizedUserId, - string TeamName, - TeamPermissionLevel? PermissionLevel, - bool? PingOnNewTicket, - bool? PingOnTicketMessage, - bool? IsEnabled, - bool? AllowAccessToWebPanel -) : IRequest, - IPermissionCheck; - -[PermissionCheck(nameof(AuthPolicy.ManageTeams))] -public sealed record ProcessCreateTeamCommand( - ulong AuthorizedUserId, - string TeamName, - TeamPermissionLevel PermissionLevel, - bool PingOnNewTicket = false, - bool PingOnTicketMessage = false, - bool AllowAccessToWebPanel = false -) : IRequest, - IPermissionCheck; - -[PermissionCheck(nameof(AuthPolicy.ManageTeams))] -public sealed record ProcessAddRoleToTeamCommand( - ulong AuthorizedUserId, - Guid Id, - DiscordRole Role) : IRequest, - IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Teams/Commands/ProcessAddRoleToTeamCommand.cs b/src/Modmail.NET/Features/Teams/Commands/ProcessAddRoleToTeamCommand.cs new file mode 100644 index 00000000..64826a0b --- /dev/null +++ b/src/Modmail.NET/Features/Teams/Commands/ProcessAddRoleToTeamCommand.cs @@ -0,0 +1,14 @@ +using DSharpPlus.Entities; +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; + +namespace Modmail.NET.Features.Teams.Commands; + +[PermissionCheck(nameof(AuthPolicy.ManageTeams))] +public sealed record ProcessAddRoleToTeamCommand( + ulong AuthorizedUserId, + Guid Id, + DiscordRole Role) : IRequest, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Teams/Commands/ProcessAddTeamMemberCommand.cs b/src/Modmail.NET/Features/Teams/Commands/ProcessAddTeamMemberCommand.cs new file mode 100644 index 00000000..13308fd4 --- /dev/null +++ b/src/Modmail.NET/Features/Teams/Commands/ProcessAddTeamMemberCommand.cs @@ -0,0 +1,13 @@ +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; + +namespace Modmail.NET.Features.Teams.Commands; + +[PermissionCheck(nameof(AuthPolicy.ManageTeams))] +public sealed record ProcessAddTeamMemberCommand( + ulong AuthorizedUserId, + Guid Id, + ulong MemberId) : IRequest, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Teams/Commands/ProcessCreateTeamCommand.cs b/src/Modmail.NET/Features/Teams/Commands/ProcessCreateTeamCommand.cs new file mode 100644 index 00000000..cd275061 --- /dev/null +++ b/src/Modmail.NET/Features/Teams/Commands/ProcessCreateTeamCommand.cs @@ -0,0 +1,19 @@ +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Permission.Static; + +namespace Modmail.NET.Features.Teams.Commands; + +[PermissionCheck(nameof(AuthPolicy.ManageTeams))] +public sealed record ProcessCreateTeamCommand( + ulong AuthorizedUserId, + string TeamName, + TeamPermissionLevel PermissionLevel, + bool PingOnNewTicket = false, + bool PingOnTicketMessage = false, + bool AllowAccessToWebPanel = false +) : IRequest, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Teams/Commands/ProcessRemoveTeamCommand.cs b/src/Modmail.NET/Features/Teams/Commands/ProcessRemoveTeamCommand.cs new file mode 100644 index 00000000..7826766a --- /dev/null +++ b/src/Modmail.NET/Features/Teams/Commands/ProcessRemoveTeamCommand.cs @@ -0,0 +1,13 @@ +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Teams.Commands; + +[PermissionCheck(nameof(AuthPolicy.ManageTeams))] +public sealed record ProcessRemoveTeamCommand( + ulong AuthorizedUserId, + Guid Id) : IRequest, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Teams/Commands/ProcessRemoveTeamMemberCommand.cs b/src/Modmail.NET/Features/Teams/Commands/ProcessRemoveTeamMemberCommand.cs new file mode 100644 index 00000000..9bc93eb7 --- /dev/null +++ b/src/Modmail.NET/Features/Teams/Commands/ProcessRemoveTeamMemberCommand.cs @@ -0,0 +1,14 @@ +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; +using Modmail.NET.Features.Teams.Static; + +namespace Modmail.NET.Features.Teams.Commands; + +[PermissionCheck(nameof(AuthPolicy.ManageTeams))] +public sealed record ProcessRemoveTeamMemberCommand( + ulong AuthorizedUserId, + ulong TeamMemberKey, + TeamMemberDataType Type) : IRequest, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Teams/Commands/ProcessRenameTeamCommand.cs b/src/Modmail.NET/Features/Teams/Commands/ProcessRenameTeamCommand.cs new file mode 100644 index 00000000..963cd892 --- /dev/null +++ b/src/Modmail.NET/Features/Teams/Commands/ProcessRenameTeamCommand.cs @@ -0,0 +1,13 @@ +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; + +namespace Modmail.NET.Features.Teams.Commands; + +[PermissionCheck(nameof(AuthPolicy.ManageTeams))] +public sealed record ProcessRenameTeamCommand( + ulong AuthorizedUserId, + Guid Id, + string NewName) : IRequest, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Teams/Commands/ProcessUpdateTeamCommand.cs b/src/Modmail.NET/Features/Teams/Commands/ProcessUpdateTeamCommand.cs new file mode 100644 index 00000000..f5f0fb39 --- /dev/null +++ b/src/Modmail.NET/Features/Teams/Commands/ProcessUpdateTeamCommand.cs @@ -0,0 +1,19 @@ +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; +using Modmail.NET.Features.Permission.Static; + +namespace Modmail.NET.Features.Teams.Commands; + +[PermissionCheck(nameof(AuthPolicy.ManageTeams))] +public sealed record ProcessUpdateTeamCommand( + ulong AuthorizedUserId, + string TeamName, + TeamPermissionLevel? PermissionLevel, + bool? PingOnNewTicket, + bool? PingOnTicketMessage, + bool? IsEnabled, + bool? AllowAccessToWebPanel +) : IRequest, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Teams/Handlers/CheckTeamExistsHandler.cs b/src/Modmail.NET/Features/Teams/Handlers/CheckTeamExistsHandler.cs index 8418ec32..175e0212 100644 --- a/src/Modmail.NET/Features/Teams/Handlers/CheckTeamExistsHandler.cs +++ b/src/Modmail.NET/Features/Teams/Handlers/CheckTeamExistsHandler.cs @@ -1,6 +1,7 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Modmail.NET.Database; +using Modmail.NET.Features.Teams.Queries; namespace Modmail.NET.Features.Teams.Handlers; diff --git a/src/Modmail.NET/Features/Teams/Handlers/CheckUserInAnyTeamHandler.cs b/src/Modmail.NET/Features/Teams/Handlers/CheckUserInAnyTeamHandler.cs index ac789db6..f7968395 100644 --- a/src/Modmail.NET/Features/Teams/Handlers/CheckUserInAnyTeamHandler.cs +++ b/src/Modmail.NET/Features/Teams/Handlers/CheckUserInAnyTeamHandler.cs @@ -1,7 +1,8 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Modmail.NET.Database; -using Modmail.NET.Features.Permission; +using Modmail.NET.Features.Permission.Queries; +using Modmail.NET.Features.Teams.Static; namespace Modmail.NET.Features.Teams.Handlers; diff --git a/src/Modmail.NET/Features/Teams/Handlers/GetTeamByNameHandler.cs b/src/Modmail.NET/Features/Teams/Handlers/GetTeamByNameHandler.cs index 1a249436..add83425 100644 --- a/src/Modmail.NET/Features/Teams/Handlers/GetTeamByNameHandler.cs +++ b/src/Modmail.NET/Features/Teams/Handlers/GetTeamByNameHandler.cs @@ -1,8 +1,10 @@ using MediatR; using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Teams.Queries; +using Modmail.NET.Language; namespace Modmail.NET.Features.Teams.Handlers; diff --git a/src/Modmail.NET/Features/Teams/Handlers/GetTeamHandler.cs b/src/Modmail.NET/Features/Teams/Handlers/GetTeamHandler.cs index 225c8fb3..11299729 100644 --- a/src/Modmail.NET/Features/Teams/Handlers/GetTeamHandler.cs +++ b/src/Modmail.NET/Features/Teams/Handlers/GetTeamHandler.cs @@ -1,7 +1,8 @@ using MediatR; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Teams.Queries; namespace Modmail.NET.Features.Teams.Handlers; diff --git a/src/Modmail.NET/Features/Teams/Handlers/GetTeamListHandler.cs b/src/Modmail.NET/Features/Teams/Handlers/GetTeamListHandler.cs index 54a7fdf1..90ea3fef 100644 --- a/src/Modmail.NET/Features/Teams/Handlers/GetTeamListHandler.cs +++ b/src/Modmail.NET/Features/Teams/Handlers/GetTeamListHandler.cs @@ -1,8 +1,10 @@ using MediatR; using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Teams.Queries; +using Modmail.NET.Language; namespace Modmail.NET.Features.Teams.Handlers; diff --git a/src/Modmail.NET/Features/Teams/Handlers/ProcessAddRoleToTeamHandler.cs b/src/Modmail.NET/Features/Teams/Handlers/ProcessAddRoleToTeamHandler.cs index f75d78b1..82031b62 100644 --- a/src/Modmail.NET/Features/Teams/Handlers/ProcessAddRoleToTeamHandler.cs +++ b/src/Modmail.NET/Features/Teams/Handlers/ProcessAddRoleToTeamHandler.cs @@ -1,9 +1,11 @@ using MediatR; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Permission; -using Modmail.NET.Utils; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Permission.Queries; +using Modmail.NET.Features.Teams.Commands; +using Modmail.NET.Features.Teams.Static; namespace Modmail.NET.Features.Teams.Handlers; diff --git a/src/Modmail.NET/Features/Teams/Handlers/ProcessAddTeamMemberHandler.cs b/src/Modmail.NET/Features/Teams/Handlers/ProcessAddTeamMemberHandler.cs index a0a63a7b..c9cd548b 100644 --- a/src/Modmail.NET/Features/Teams/Handlers/ProcessAddTeamMemberHandler.cs +++ b/src/Modmail.NET/Features/Teams/Handlers/ProcessAddTeamMemberHandler.cs @@ -1,9 +1,12 @@ using MediatR; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Permission; -using Modmail.NET.Utils; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Permission.Queries; +using Modmail.NET.Features.Teams.Commands; +using Modmail.NET.Features.Teams.Queries; +using Modmail.NET.Features.Teams.Static; namespace Modmail.NET.Features.Teams.Handlers; diff --git a/src/Modmail.NET/Features/Teams/Handlers/ProcessCreateTeamHandler.cs b/src/Modmail.NET/Features/Teams/Handlers/ProcessCreateTeamHandler.cs index 74eb2550..12c4c1db 100644 --- a/src/Modmail.NET/Features/Teams/Handlers/ProcessCreateTeamHandler.cs +++ b/src/Modmail.NET/Features/Teams/Handlers/ProcessCreateTeamHandler.cs @@ -1,8 +1,11 @@ using MediatR; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Permission; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Permission.Queries; +using Modmail.NET.Features.Permission.Static; +using Modmail.NET.Features.Teams.Commands; +using Modmail.NET.Features.Teams.Queries; namespace Modmail.NET.Features.Teams.Handlers; diff --git a/src/Modmail.NET/Features/Teams/Handlers/ProcessRemoveTeamHandler.cs b/src/Modmail.NET/Features/Teams/Handlers/ProcessRemoveTeamHandler.cs index 9f2e4d23..b4570083 100644 --- a/src/Modmail.NET/Features/Teams/Handlers/ProcessRemoveTeamHandler.cs +++ b/src/Modmail.NET/Features/Teams/Handlers/ProcessRemoveTeamHandler.cs @@ -1,7 +1,9 @@ using MediatR; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Teams.Commands; +using Modmail.NET.Features.Teams.Queries; namespace Modmail.NET.Features.Teams.Handlers; diff --git a/src/Modmail.NET/Features/Teams/Handlers/ProcessRemoveTeamMemberHandler.cs b/src/Modmail.NET/Features/Teams/Handlers/ProcessRemoveTeamMemberHandler.cs index cd7737cb..148f6d33 100644 --- a/src/Modmail.NET/Features/Teams/Handlers/ProcessRemoveTeamMemberHandler.cs +++ b/src/Modmail.NET/Features/Teams/Handlers/ProcessRemoveTeamMemberHandler.cs @@ -1,7 +1,9 @@ using MediatR; using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; +using Modmail.NET.Features.Teams.Commands; +using Modmail.NET.Language; namespace Modmail.NET.Features.Teams.Handlers; diff --git a/src/Modmail.NET/Features/Teams/Handlers/ProcessRenameTeamHandler.cs b/src/Modmail.NET/Features/Teams/Handlers/ProcessRenameTeamHandler.cs index 90f2731f..c84a7f6f 100644 --- a/src/Modmail.NET/Features/Teams/Handlers/ProcessRenameTeamHandler.cs +++ b/src/Modmail.NET/Features/Teams/Handlers/ProcessRenameTeamHandler.cs @@ -1,6 +1,8 @@ using MediatR; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; +using Modmail.NET.Features.Teams.Commands; +using Modmail.NET.Features.Teams.Queries; namespace Modmail.NET.Features.Teams.Handlers; diff --git a/src/Modmail.NET/Features/Teams/Handlers/ProcessUpdateTeamHandler.cs b/src/Modmail.NET/Features/Teams/Handlers/ProcessUpdateTeamHandler.cs index 09c0b913..dbc1ad6a 100644 --- a/src/Modmail.NET/Features/Teams/Handlers/ProcessUpdateTeamHandler.cs +++ b/src/Modmail.NET/Features/Teams/Handlers/ProcessUpdateTeamHandler.cs @@ -1,6 +1,8 @@ using MediatR; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; +using Modmail.NET.Features.Teams.Commands; +using Modmail.NET.Features.Teams.Queries; namespace Modmail.NET.Features.Teams.Handlers; diff --git a/src/Modmail.NET/Features/Teams/Queries.cs b/src/Modmail.NET/Features/Teams/Queries.cs deleted file mode 100644 index 61b668e4..00000000 --- a/src/Modmail.NET/Features/Teams/Queries.cs +++ /dev/null @@ -1,19 +0,0 @@ -using MediatR; -using Modmail.NET.Abstract; -using Modmail.NET.Attributes; -using Modmail.NET.Entities; - -namespace Modmail.NET.Features.Teams; - -[PermissionCheck(nameof(AuthPolicy.ManageTeams))] -public sealed record CheckTeamExistsQuery(ulong AuthorizedUserId, string Name) : IRequest, - IPermissionCheck; - -[PermissionCheck(nameof(AuthPolicy.ManageTeams))] -public sealed record GetTeamListQuery(ulong AuthorizedUserId, bool ThrowIfEmpty = false) : IRequest>; - -[PermissionCheck(nameof(AuthPolicy.ManageTeams))] -public sealed record GetTeamByNameQuery(ulong AuthorizedUserId, string Name, bool AllowNull = false) : IRequest; - -[PermissionCheck(nameof(AuthPolicy.ManageTeams))] -public sealed record GetTeamQuery(ulong AuthorizedUserId, Guid Id, bool AllowNull = false) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Teams/Queries/CheckTeamExistsQuery.cs b/src/Modmail.NET/Features/Teams/Queries/CheckTeamExistsQuery.cs new file mode 100644 index 00000000..d5de1b60 --- /dev/null +++ b/src/Modmail.NET/Features/Teams/Queries/CheckTeamExistsQuery.cs @@ -0,0 +1,10 @@ +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; + +namespace Modmail.NET.Features.Teams.Queries; + +[PermissionCheck(nameof(AuthPolicy.ManageTeams))] +public sealed record CheckTeamExistsQuery(ulong AuthorizedUserId, string Name) : IRequest, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Teams/Queries/GetTeamByNameQuery.cs b/src/Modmail.NET/Features/Teams/Queries/GetTeamByNameQuery.cs new file mode 100644 index 00000000..49aaff67 --- /dev/null +++ b/src/Modmail.NET/Features/Teams/Queries/GetTeamByNameQuery.cs @@ -0,0 +1,9 @@ +using MediatR; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Teams.Queries; + +[PermissionCheck(nameof(AuthPolicy.ManageTeams))] +public sealed record GetTeamByNameQuery(ulong AuthorizedUserId, string Name, bool AllowNull = false) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Teams/Queries/GetTeamListQuery.cs b/src/Modmail.NET/Features/Teams/Queries/GetTeamListQuery.cs new file mode 100644 index 00000000..46fea692 --- /dev/null +++ b/src/Modmail.NET/Features/Teams/Queries/GetTeamListQuery.cs @@ -0,0 +1,9 @@ +using MediatR; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Teams.Queries; + +[PermissionCheck(nameof(AuthPolicy.ManageTeams))] +public sealed record GetTeamListQuery(ulong AuthorizedUserId, bool ThrowIfEmpty = false) : IRequest>; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Teams/Queries/GetTeamQuery.cs b/src/Modmail.NET/Features/Teams/Queries/GetTeamQuery.cs new file mode 100644 index 00000000..56722879 --- /dev/null +++ b/src/Modmail.NET/Features/Teams/Queries/GetTeamQuery.cs @@ -0,0 +1,9 @@ +using MediatR; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Teams.Queries; + +[PermissionCheck(nameof(AuthPolicy.ManageTeams))] +public sealed record GetTeamQuery(ulong AuthorizedUserId, Guid Id, bool AllowNull = false) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Static/TeamMemberDataType.cs b/src/Modmail.NET/Features/Teams/Static/TeamMemberDataType.cs similarity index 80% rename from src/Modmail.NET/Static/TeamMemberDataType.cs rename to src/Modmail.NET/Features/Teams/Static/TeamMemberDataType.cs index 80b3853f..38697d2f 100644 --- a/src/Modmail.NET/Static/TeamMemberDataType.cs +++ b/src/Modmail.NET/Features/Teams/Static/TeamMemberDataType.cs @@ -1,6 +1,6 @@ using DSharpPlus.Commands.Processors.SlashCommands.ArgumentModifiers; -namespace Modmail.NET.Static; +namespace Modmail.NET.Features.Teams.Static; public enum TeamMemberDataType { diff --git a/src/Modmail.NET/Features/Ticket/Commands.cs b/src/Modmail.NET/Features/Ticket/Commands.cs deleted file mode 100644 index 4e6c7730..00000000 --- a/src/Modmail.NET/Features/Ticket/Commands.cs +++ /dev/null @@ -1,62 +0,0 @@ -using DSharpPlus.Entities; -using MediatR; - -namespace Modmail.NET.Features.Ticket; - -public sealed record ProcessCloseTicketCommand( - Guid TicketId, - ulong CloserUserId = 0, - string CloseReason = null, - DiscordChannel ModChatChannel = null, - bool DontSendFeedbackMessage = false) : IRequest; - -public sealed record ProcessChangePriorityCommand( - Guid TicketId, - ulong ModUserId, - TicketPriority NewPriority, - DiscordChannel TicketChannel = null) : IRequest; - -public sealed record ProcessUserSentMessageCommand( - Guid TicketId, - DiscordMessage Message, - DiscordChannel PrivateChannel = null) : IRequest; - -public sealed record ProcessCreateNewTicketCommand( - DiscordUser User, - DiscordChannel PrivateChannel, - DiscordMessage Message) : IRequest; - -public sealed record ProcessModSendMessageCommand( - Guid TicketId, - DiscordUser ModUser, - DiscordMessage Message, - DiscordChannel Channel, - DiscordGuild Guild -) : IRequest; - -public sealed record ProcessAddFeedbackCommand( - Guid TicketId, - int StarCount, - string TextInput, - DiscordMessage FeedbackMessage -) : IRequest; - -public sealed record ProcessAddNoteCommand( - Guid TicketId, - ulong UserId, - string Note -) : IRequest; - -public sealed record ProcessToggleAnonymousCommand( - Guid TicketId, - DiscordChannel TicketChannel = null -) : IRequest; - -public sealed record ProcessChangeTicketTypeCommand( - Guid TicketId, - string Type, - DiscordChannel TicketChannel = null, - DiscordChannel PrivateChannel = null, - DiscordMessage PrivateMessageWithComponent = null, - ulong UserId = 0 -) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Commands/ProcessAddFeedbackCommand.cs b/src/Modmail.NET/Features/Ticket/Commands/ProcessAddFeedbackCommand.cs new file mode 100644 index 00000000..88184072 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Commands/ProcessAddFeedbackCommand.cs @@ -0,0 +1,11 @@ +using DSharpPlus.Entities; +using MediatR; + +namespace Modmail.NET.Features.Ticket.Commands; + +public sealed record ProcessAddFeedbackCommand( + Guid TicketId, + int StarCount, + string TextInput, + DiscordMessage FeedbackMessage +) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Commands/ProcessAddNoteCommand.cs b/src/Modmail.NET/Features/Ticket/Commands/ProcessAddNoteCommand.cs new file mode 100644 index 00000000..54d485a8 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Commands/ProcessAddNoteCommand.cs @@ -0,0 +1,9 @@ +using MediatR; + +namespace Modmail.NET.Features.Ticket.Commands; + +public sealed record ProcessAddNoteCommand( + Guid TicketId, + ulong UserId, + string Note +) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Commands/ProcessChangePriorityCommand.cs b/src/Modmail.NET/Features/Ticket/Commands/ProcessChangePriorityCommand.cs new file mode 100644 index 00000000..d293d4f9 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Commands/ProcessChangePriorityCommand.cs @@ -0,0 +1,11 @@ +using DSharpPlus.Entities; +using MediatR; +using Modmail.NET.Features.Ticket.Static; + +namespace Modmail.NET.Features.Ticket.Commands; + +public sealed record ProcessChangePriorityCommand( + Guid TicketId, + ulong ModUserId, + TicketPriority NewPriority, + DiscordChannel TicketChannel = null) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Commands/ProcessChangeTicketTypeCommand.cs b/src/Modmail.NET/Features/Ticket/Commands/ProcessChangeTicketTypeCommand.cs new file mode 100644 index 00000000..4cfdd088 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Commands/ProcessChangeTicketTypeCommand.cs @@ -0,0 +1,13 @@ +using DSharpPlus.Entities; +using MediatR; + +namespace Modmail.NET.Features.Ticket.Commands; + +public sealed record ProcessChangeTicketTypeCommand( + Guid TicketId, + string Type, + DiscordChannel TicketChannel = null, + DiscordChannel PrivateChannel = null, + DiscordMessage PrivateMessageWithComponent = null, + ulong UserId = 0 +) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Commands/ProcessCloseTicketCommand.cs b/src/Modmail.NET/Features/Ticket/Commands/ProcessCloseTicketCommand.cs new file mode 100644 index 00000000..6d9cf2ad --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Commands/ProcessCloseTicketCommand.cs @@ -0,0 +1,11 @@ +using DSharpPlus.Entities; +using MediatR; + +namespace Modmail.NET.Features.Ticket.Commands; + +public sealed record ProcessCloseTicketCommand( + Guid TicketId, + ulong CloserUserId = 0, + string CloseReason = null, + DiscordChannel ModChatChannel = null, + bool DontSendFeedbackMessage = false) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Commands/ProcessCreateNewTicketCommand.cs b/src/Modmail.NET/Features/Ticket/Commands/ProcessCreateNewTicketCommand.cs new file mode 100644 index 00000000..db35006f --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Commands/ProcessCreateNewTicketCommand.cs @@ -0,0 +1,9 @@ +using DSharpPlus.Entities; +using MediatR; + +namespace Modmail.NET.Features.Ticket.Commands; + +public sealed record ProcessCreateNewTicketCommand( + DiscordUser User, + DiscordChannel PrivateChannel, + DiscordMessage Message) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Commands/ProcessCreateTicketTypeCommand.cs b/src/Modmail.NET/Features/Ticket/Commands/ProcessCreateTicketTypeCommand.cs new file mode 100644 index 00000000..51e466d7 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Commands/ProcessCreateTicketTypeCommand.cs @@ -0,0 +1,15 @@ +using MediatR; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; + +namespace Modmail.NET.Features.Ticket.Commands; + +[PermissionCheck(nameof(AuthPolicy.ManageTicketTypes))] +public sealed record ProcessCreateTicketTypeCommand( + ulong AuthorizedUserId, + string Name, + string Emoji, + string Description, + long Order, + string EmbedMessageTitle, + string EmbedMessageContent) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Commands/ProcessModSendMessageCommand.cs b/src/Modmail.NET/Features/Ticket/Commands/ProcessModSendMessageCommand.cs new file mode 100644 index 00000000..c3f85be9 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Commands/ProcessModSendMessageCommand.cs @@ -0,0 +1,12 @@ +using DSharpPlus.Entities; +using MediatR; + +namespace Modmail.NET.Features.Ticket.Commands; + +public sealed record ProcessModSendMessageCommand( + Guid TicketId, + DiscordUser ModUser, + DiscordMessage Message, + DiscordChannel Channel, + DiscordGuild Guild +) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Commands/ProcessRemoveTicketTypeCommand.cs b/src/Modmail.NET/Features/Ticket/Commands/ProcessRemoveTicketTypeCommand.cs new file mode 100644 index 00000000..d37a3e8a --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Commands/ProcessRemoveTicketTypeCommand.cs @@ -0,0 +1,11 @@ +using MediatR; +using Modmail.NET.Abstract; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Ticket.Commands; + +[PermissionCheck(nameof(AuthPolicy.ManageTicketTypes))] +public sealed record ProcessRemoveTicketTypeCommand(ulong AuthorizedUserId, Guid Id) : IRequest, + IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Commands/ProcessToggleAnonymousCommand.cs b/src/Modmail.NET/Features/Ticket/Commands/ProcessToggleAnonymousCommand.cs new file mode 100644 index 00000000..c8440f91 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Commands/ProcessToggleAnonymousCommand.cs @@ -0,0 +1,9 @@ +using DSharpPlus.Entities; +using MediatR; + +namespace Modmail.NET.Features.Ticket.Commands; + +public sealed record ProcessToggleAnonymousCommand( + Guid TicketId, + DiscordChannel TicketChannel = null +) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Commands/ProcessUpdateTicketTypeCommand.cs b/src/Modmail.NET/Features/Ticket/Commands/ProcessUpdateTicketTypeCommand.cs new file mode 100644 index 00000000..afa6bd1a --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Commands/ProcessUpdateTicketTypeCommand.cs @@ -0,0 +1,9 @@ +using MediatR; +using Modmail.NET.Attributes; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Ticket.Commands; + +[PermissionCheck(nameof(AuthPolicy.ManageTicketTypes))] +public sealed record ProcessUpdateTicketTypeCommand(ulong AuthorizedUserId, TicketType TicketType) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Commands/ProcessUserSentMessageCommand.cs b/src/Modmail.NET/Features/Ticket/Commands/ProcessUserSentMessageCommand.cs new file mode 100644 index 00000000..d089b15a --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Commands/ProcessUserSentMessageCommand.cs @@ -0,0 +1,9 @@ +using DSharpPlus.Entities; +using MediatR; + +namespace Modmail.NET.Features.Ticket.Commands; + +public sealed record ProcessUserSentMessageCommand( + Guid TicketId, + DiscordMessage Message, + DiscordChannel PrivateChannel = null) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/TicketType/Handlers/CheckTicketTypeExistsHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/CheckTicketTypeExistsHandler.cs similarity index 86% rename from src/Modmail.NET/Features/TicketType/Handlers/CheckTicketTypeExistsHandler.cs rename to src/Modmail.NET/Features/Ticket/Handlers/CheckTicketTypeExistsHandler.cs index 9ac3032f..44eeb37a 100644 --- a/src/Modmail.NET/Features/TicketType/Handlers/CheckTicketTypeExistsHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/CheckTicketTypeExistsHandler.cs @@ -1,8 +1,9 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Modmail.NET.Database; +using Modmail.NET.Features.Ticket.Queries; -namespace Modmail.NET.Features.TicketType.Handlers; +namespace Modmail.NET.Features.Ticket.Handlers; public class CheckTicketTypeExistsHandler : IRequestHandler { diff --git a/src/Modmail.NET/Features/Ticket/Handlers/GetActiveTicketByUserIdHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/GetActiveTicketByUserIdHandler.cs index 03c295e3..909b110c 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/GetActiveTicketByUserIdHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/GetActiveTicketByUserIdHandler.cs @@ -1,11 +1,13 @@ using MediatR; using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Language; namespace Modmail.NET.Features.Ticket.Handlers; -public class GetActiveTicketByUserIdHandler : IRequestHandler +public class GetActiveTicketByUserIdHandler : IRequestHandler { private readonly ModmailDbContext _dbContext; @@ -13,7 +15,7 @@ public GetActiveTicketByUserIdHandler(ModmailDbContext dbContext) { _dbContext = dbContext; } - public async Task Handle(GetTicketByUserIdQuery request, CancellationToken cancellationToken) { + public async Task Handle(GetTicketByUserIdQuery request, CancellationToken cancellationToken) { var ticket = await _dbContext.Tickets .FirstOrDefaultAsync(x => x.OpenerUserId == request.UserId && !x.ClosedDateUtc.HasValue, cancellationToken); if (!request.AllowNull && ticket is null) throw new NotFoundException(LangKeys.Ticket); diff --git a/src/Modmail.NET/Features/Ticket/Handlers/GetTicketHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/GetTicketHandler.cs index 45477569..63f9dd1e 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/GetTicketHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/GetTicketHandler.cs @@ -1,10 +1,12 @@ using MediatR; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Language; namespace Modmail.NET.Features.Ticket.Handlers; -public class GetTicketHandler : IRequestHandler +public class GetTicketHandler : IRequestHandler { private readonly ModmailDbContext _dbContext; @@ -12,7 +14,7 @@ public GetTicketHandler(ModmailDbContext dbContext) { _dbContext = dbContext; } - public async Task Handle(GetTicketQuery request, CancellationToken cancellationToken) { + public async Task Handle(GetTicketQuery request, CancellationToken cancellationToken) { var ticket = await _dbContext.Tickets.FindAsync([request.Id], cancellationToken); if (ticket is null) { if (!request.AllowNull) throw new NotFoundException(LangKeys.Ticket); diff --git a/src/Modmail.NET/Features/Ticket/Handlers/GetTicketListByTypeHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/GetTicketListByTypeHandler.cs index 0ddc6fe0..379b2fd3 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/GetTicketListByTypeHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/GetTicketListByTypeHandler.cs @@ -1,10 +1,11 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Modmail.NET.Database; +using Modmail.NET.Features.Ticket.Queries; namespace Modmail.NET.Features.Ticket.Handlers; -public class GetTicketListByTypeHandler : IRequestHandler> +public class GetTicketListByTypeHandler : IRequestHandler> { private readonly ModmailDbContext _dbContext; @@ -12,7 +13,7 @@ public GetTicketListByTypeHandler(ModmailDbContext dbContext) { _dbContext = dbContext; } - public async Task> Handle(GetTicketListByTypeQuery request, CancellationToken cancellationToken) { + public async Task> Handle(GetTicketListByTypeQuery request, CancellationToken cancellationToken) { var tickets = await _dbContext.Tickets .Where(x => x.TicketTypeId == request.TicketTypeId) .ToListAsync(cancellationToken); diff --git a/src/Modmail.NET/Features/TicketType/Handlers/GetTicketTypeByChannelIdHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/GetTicketTypeByChannelIdHandler.cs similarity index 64% rename from src/Modmail.NET/Features/TicketType/Handlers/GetTicketTypeByChannelIdHandler.cs rename to src/Modmail.NET/Features/Ticket/Handlers/GetTicketTypeByChannelIdHandler.cs index 8c84325f..15020039 100644 --- a/src/Modmail.NET/Features/TicketType/Handlers/GetTicketTypeByChannelIdHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/GetTicketTypeByChannelIdHandler.cs @@ -1,11 +1,14 @@ using MediatR; using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Language; -namespace Modmail.NET.Features.TicketType.Handlers; +namespace Modmail.NET.Features.Ticket.Handlers; -public class GetTicketTypeByChannelIdHandler : IRequestHandler +public class GetTicketTypeByChannelIdHandler : IRequestHandler { private readonly ModmailDbContext _dbContext; @@ -13,7 +16,7 @@ public GetTicketTypeByChannelIdHandler(ModmailDbContext dbContext) { _dbContext = dbContext; } - public async Task Handle(GetTicketTypeByChannelIdQuery request, CancellationToken cancellationToken) { + public async Task Handle(GetTicketTypeByChannelIdQuery request, CancellationToken cancellationToken) { var result = await _dbContext.Tickets.Where(x => x.ModMessageChannelId == request.ChannelId) .Select(x => x.TicketType) .FirstOrDefaultAsync(cancellationToken); diff --git a/src/Modmail.NET/Features/TicketType/Handlers/GetTicketTypeBySearchHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/GetTicketTypeBySearchHandler.cs similarity index 63% rename from src/Modmail.NET/Features/TicketType/Handlers/GetTicketTypeBySearchHandler.cs rename to src/Modmail.NET/Features/Ticket/Handlers/GetTicketTypeBySearchHandler.cs index 066cbf10..1c68ce86 100644 --- a/src/Modmail.NET/Features/TicketType/Handlers/GetTicketTypeBySearchHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/GetTicketTypeBySearchHandler.cs @@ -1,11 +1,14 @@ using MediatR; using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Language; -namespace Modmail.NET.Features.TicketType.Handlers; +namespace Modmail.NET.Features.Ticket.Handlers; -public class GetTicketTypeBySearchHandler : IRequestHandler +public class GetTicketTypeBySearchHandler : IRequestHandler { private readonly ModmailDbContext _dbContext; @@ -13,7 +16,7 @@ public GetTicketTypeBySearchHandler(ModmailDbContext dbContext) { _dbContext = dbContext; } - public async Task Handle(GetTicketTypeBySearchQuery request, CancellationToken cancellationToken) { + public async Task Handle(GetTicketTypeBySearchQuery request, CancellationToken cancellationToken) { var result = await _dbContext.TicketTypes.FirstOrDefaultAsync(x => x.Key == request.NameOrKey || x.Name == request.NameOrKey, cancellationToken); if (!request.AllowNull && result is null) throw new NotFoundWithException(LangKeys.TicketType, request.NameOrKey); return result; diff --git a/src/Modmail.NET/Features/TicketType/Handlers/GetTicketTypeHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/GetTicketTypeHandler.cs similarity index 60% rename from src/Modmail.NET/Features/TicketType/Handlers/GetTicketTypeHandler.cs rename to src/Modmail.NET/Features/Ticket/Handlers/GetTicketTypeHandler.cs index b7159173..4bad5f5e 100644 --- a/src/Modmail.NET/Features/TicketType/Handlers/GetTicketTypeHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/GetTicketTypeHandler.cs @@ -1,10 +1,12 @@ using MediatR; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Ticket.Queries; -namespace Modmail.NET.Features.TicketType.Handlers; +namespace Modmail.NET.Features.Ticket.Handlers; -public class GetTicketTypeHandler : IRequestHandler +public class GetTicketTypeHandler : IRequestHandler { private readonly ModmailDbContext _dbContext; @@ -12,7 +14,7 @@ public GetTicketTypeHandler(ModmailDbContext dbContext) { _dbContext = dbContext; } - public async Task Handle(GetTicketTypeQuery request, CancellationToken cancellationToken) { + public async Task Handle(GetTicketTypeQuery request, CancellationToken cancellationToken) { var data = await _dbContext.TicketTypes.FindAsync([request.Id], cancellationToken); if (!request.AllowNull && data is null) throw new TicketTypeNotExistsException(); diff --git a/src/Modmail.NET/Features/TicketType/Handlers/GetTicketTypeListHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/GetTicketTypeListHandler.cs similarity index 63% rename from src/Modmail.NET/Features/TicketType/Handlers/GetTicketTypeListHandler.cs rename to src/Modmail.NET/Features/Ticket/Handlers/GetTicketTypeListHandler.cs index 29846dfd..5865dab8 100644 --- a/src/Modmail.NET/Features/TicketType/Handlers/GetTicketTypeListHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/GetTicketTypeListHandler.cs @@ -1,10 +1,12 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Modmail.NET.Database; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Ticket.Queries; -namespace Modmail.NET.Features.TicketType.Handlers; +namespace Modmail.NET.Features.Ticket.Handlers; -public class GetTicketTypeListHandler : IRequestHandler> +public class GetTicketTypeListHandler : IRequestHandler> { private readonly ModmailDbContext _dbContext; @@ -12,7 +14,7 @@ public GetTicketTypeListHandler(ModmailDbContext dbContext) { _dbContext = dbContext; } - public async Task> Handle(GetTicketTypeListQuery request, CancellationToken cancellationToken) { + public async Task> Handle(GetTicketTypeListQuery request, CancellationToken cancellationToken) { var query = _dbContext.TicketTypes.AsQueryable(); if (request.OnlyActive) query = query.Where(x => x.IsEnabled == true); diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessAddFeedbackHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessAddFeedbackHandler.cs index b5925260..030c2e72 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessAddFeedbackHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessAddFeedbackHandler.cs @@ -1,7 +1,10 @@ using MediatR; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Guild; +using Modmail.NET.Features.Guild.Queries; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Helpers; +using Modmail.NET.Features.Ticket.Queries; namespace Modmail.NET.Features.Ticket.Handlers; @@ -34,6 +37,6 @@ public async Task Handle(ProcessAddFeedbackCommand request, CancellationToken ca var affected = await _dbContext.SaveChangesAsync(cancellationToken); if (affected == 0) throw new DbInternalException(); - _ = Task.Run(async () => { await request.FeedbackMessage.ModifyAsync(x => { x.AddEmbed(UserResponses.FeedbackReceivedUpdateMessage(ticket)); }); }, cancellationToken); + _ = Task.Run(async () => { await request.FeedbackMessage.ModifyAsync(x => { x.AddEmbed(TicketBotMessages.User.FeedbackReceivedUpdateMessage(ticket)); }); }, cancellationToken); } } \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessAddNoteHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessAddNoteHandler.cs index 637d10d2..101e7186 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessAddNoteHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessAddNoteHandler.cs @@ -1,8 +1,11 @@ using DSharpPlus.Exceptions; using MediatR; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Features.UserInfo; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Helpers; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Features.User.Queries; namespace Modmail.NET.Features.Ticket.Handlers; @@ -33,7 +36,7 @@ public async Task Handle(ProcessAddNoteCommand request, CancellationToken cancel try { var user = await _sender.Send(new GetDiscordUserInfoQuery(request.UserId), cancellationToken); var mailChannel = await _bot.Client.GetChannelAsync(ticket.ModMessageChannelId); - await mailChannel.SendMessageAsync(TicketResponses.NoteAdded(noteEntity, user)); + await mailChannel.SendMessageAsync(TicketBotMessages.Ticket.NoteAdded(noteEntity, user)); } catch (NotFoundException) { //ignored diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessChangePriorityHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessChangePriorityHandler.cs index 1fd5ed0c..3ac9adb6 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessChangePriorityHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessChangePriorityHandler.cs @@ -1,9 +1,13 @@ using MediatR; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Bot; -using Modmail.NET.Features.Guild; -using Modmail.NET.Features.UserInfo; +using Modmail.NET.Features.DiscordBot.Queries; +using Modmail.NET.Features.Guild.Queries; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Helpers; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Features.Ticket.Static; +using Modmail.NET.Features.User.Queries; using NotFoundException = DSharpPlus.Exceptions.NotFoundException; namespace Modmail.NET.Features.Ticket.Handlers; @@ -41,26 +45,26 @@ public async Task Handle(ProcessChangePriorityCommand request, CancellationToken try { var guildOption = await _sender.Send(new GetGuildOptionQuery(false), cancellationToken); var privateChannel = await _bot.Client.GetChannelAsync(ticket.PrivateMessageChannelId); - await privateChannel.SendMessageAsync(UserResponses.TicketPriorityChanged(guildOption, modUser, ticket, oldPriority, request.NewPriority)); + await privateChannel.SendMessageAsync(TicketBotMessages.User.TicketPriorityChanged(guildOption, modUser, ticket, oldPriority, request.NewPriority)); } catch (NotFoundException) { //ignored } var logChannel = await _sender.Send(new GetDiscordLogChannelQuery(), cancellationToken); - await logChannel.SendMessageAsync(LogResponses.TicketPriorityChanged(modUser, ticket, oldPriority, request.NewPriority)); + await logChannel.SendMessageAsync(LogBotMessages.TicketPriorityChanged(modUser, ticket, oldPriority, request.NewPriority)); try { var ticketChannel = request.TicketChannel ?? await _bot.Client.GetChannelAsync(ticket.ModMessageChannelId); var newChName = request.NewPriority switch { - TicketPriority.Normal => Const.NormalPriorityEmoji + string.Format(Const.TicketNameTemplate, ticket.OpenerUser?.Username.Trim()), - TicketPriority.High => Const.HighPriorityEmoji + string.Format(Const.TicketNameTemplate, ticket.OpenerUser?.Username.Trim()), - TicketPriority.Low => Const.LowPriorityEmoji + string.Format(Const.TicketNameTemplate, ticket.OpenerUser?.Username.Trim()), + TicketPriority.Normal => TicketConstants.NormalPriorityEmoji + string.Format(TicketConstants.TicketNameTemplate, ticket.OpenerUser?.Username.Trim()), + TicketPriority.High => TicketConstants.HighPriorityEmoji + string.Format(TicketConstants.TicketNameTemplate, ticket.OpenerUser?.Username.Trim()), + TicketPriority.Low => TicketConstants.LowPriorityEmoji + string.Format(TicketConstants.TicketNameTemplate, ticket.OpenerUser?.Username.Trim()), _ => "" }; await ticketChannel.ModifyAsync(x => { x.Name = newChName; }); - await ticketChannel.SendMessageAsync(TicketResponses.TicketPriorityChanged(modUser, ticket, oldPriority, request.NewPriority)); + await ticketChannel.SendMessageAsync(TicketBotMessages.Ticket.TicketPriorityChanged(modUser, ticket, oldPriority, request.NewPriority)); } catch (NotFoundException) { //ignored diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessChangeTicketTypeHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessChangeTicketTypeHandler.cs index 8bc5585c..05bd4af1 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessChangeTicketTypeHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessChangeTicketTypeHandler.cs @@ -1,10 +1,13 @@ using DSharpPlus.Entities; using MediatR; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.TicketType; -using Modmail.NET.Features.UserInfo; -using Modmail.NET.Jobs; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Helpers; +using Modmail.NET.Features.Ticket.Jobs; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Features.User.Queries; +using Modmail.NET.Language; using NotFoundException = DSharpPlus.Exceptions.NotFoundException; namespace Modmail.NET.Features.Ticket.Handlers; @@ -52,7 +55,7 @@ await Task.Run(async () => { var userInfo = await _sender.Send(new GetDiscordUserInfoQuery(userId), cancellationToken); var ticketChannel = request.TicketChannel ?? await _bot.Client.GetChannelAsync(ticket.ModMessageChannelId); - if (ticketChannel is not null) await ticketChannel.SendMessageAsync(TicketResponses.TicketTypeChanged(userInfo, ticketType)); + if (ticketChannel is not null) await ticketChannel.SendMessageAsync(TicketBotMessages.Ticket.TicketTypeChanged(userInfo, ticketType)); if (ticket.BotTicketCreatedMessageInDmId != 0) { try { diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessCloseTicketHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessCloseTicketHandler.cs index 64a7654a..a6438a51 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessCloseTicketHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessCloseTicketHandler.cs @@ -1,10 +1,14 @@ using DSharpPlus.Exceptions; using MediatR; using Microsoft.Extensions.Options; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Features.Bot; -using Modmail.NET.Features.Guild; -using Modmail.NET.Utils; +using Modmail.NET.Features.DiscordBot.Queries; +using Modmail.NET.Features.Guild.Queries; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Helpers; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Language; namespace Modmail.NET.Features.Ticket.Handlers; @@ -65,15 +69,15 @@ public async Task Handle(ProcessCloseTicketCommand request, CancellationToken ca await modChatChannel.DeleteAsync(LangProvider.This.GetTranslation(LangKeys.TicketClosed)); try { var pmChannel = await _bot.Client.GetChannelAsync(ticket.PrivateMessageChannelId); - await pmChannel.SendMessageAsync(UserResponses.YourTicketHasBeenClosed(ticket, guildOption, transcriptUri)); - if (guildOption.TakeFeedbackAfterClosing && !request.DontSendFeedbackMessage) await pmChannel.SendMessageAsync(UserResponses.GiveFeedbackMessage(ticket, guildOption)); + await pmChannel.SendMessageAsync(TicketBotMessages.User.YourTicketHasBeenClosed(ticket, guildOption, transcriptUri)); + if (guildOption.TakeFeedbackAfterClosing && !request.DontSendFeedbackMessage) await pmChannel.SendMessageAsync(TicketBotMessages.User.GiveFeedbackMessage(ticket, guildOption)); } catch (NotFoundException) { //ignored } var logChannel = await _sender.Send(new GetDiscordLogChannelQuery(), cancellationToken); - await logChannel.SendMessageAsync(LogResponses.TicketClosed(ticket)); + await logChannel.SendMessageAsync(LogBotMessages.TicketClosed(ticket)); }, cancellationToken); } } \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateNewTicketHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateNewTicketHandler.cs index a1932b92..48ee152c 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateNewTicketHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateNewTicketHandler.cs @@ -1,15 +1,18 @@ using DSharpPlus.Entities; using MediatR; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Bot; -using Modmail.NET.Features.Guild; -using Modmail.NET.Features.Permission; -using Modmail.NET.Features.TicketType; -using Modmail.NET.Jobs; -using Modmail.NET.Services; -using Modmail.NET.Utils; +using Modmail.NET.Features.DiscordBot.Queries; +using Modmail.NET.Features.Guild.Queries; +using Modmail.NET.Features.Permission.Queries; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Helpers; +using Modmail.NET.Features.Ticket.Jobs; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Features.Ticket.Services; +using Modmail.NET.Features.Ticket.Static; +using TicketMessage = Modmail.NET.Database.Entities.TicketMessage; namespace Modmail.NET.Features.Ticket.Handlers; @@ -38,7 +41,7 @@ public async Task Handle(ProcessCreateNewTicketCommand request, CancellationToke var guild = await _sender.Send(new GetDiscordMainGuildQuery(), cancellationToken); //make new privateChannel - var channelName = string.Format(Const.TicketNameTemplate, request.User.Username.Trim()); + var channelName = string.Format(TicketConstants.TicketNameTemplate, request.User.Username.Trim()); var category = await _bot.Client.GetChannelAsync(guildOption.CategoryId); var ticketId = Guid.CreateVersion7(); @@ -56,12 +59,12 @@ public async Task Handle(ProcessCreateNewTicketCommand request, CancellationToke var pingOnNewTicket = permissions.Where(x => x.PingOnNewTicket).ToArray(); - var msg = TicketResponses.NewTicket(member, ticketId); + var msg = TicketBotMessages.Ticket.NewTicket(member, ticketId); msg.WithContent(UtilMention.GetMentionsMessageString(pingOnNewTicket)); var ticketMessage = TicketMessage.MapFrom(ticketId, request.Message, false); - var ticket = new Entities.Ticket { + var ticket = new Database.Entities.Ticket { OpenerUserId = request.User.Id, ModMessageChannelId = mailChannel.Id, RegisterDateUtc = UtilDate.GetNow(), @@ -85,10 +88,10 @@ public async Task Handle(ProcessCreateNewTicketCommand request, CancellationToke var ticketTypes = await _sender.Send(new GetTicketTypeListQuery(true), cancellationToken); - var ticketCreatedMessage = await request.PrivateChannel.SendMessageAsync(UserResponses.YouHaveCreatedNewTicket(guild, - guildOption, - ticketTypes, - ticketId)); + var ticketCreatedMessage = await request.PrivateChannel.SendMessageAsync(TicketBotMessages.User.YouHaveCreatedNewTicket(guild, + guildOption, + ticketTypes, + ticketId)); _ticketTypeSelectionTimeoutJob.AddMessage(ticketCreatedMessage); @@ -103,13 +106,13 @@ public async Task Handle(ProcessCreateNewTicketCommand request, CancellationToke await _attachmentDownloadService.Handle(attachment.Id, attachment.Url, Path.GetExtension(attachment.FileName)); await mailChannel.SendMessageAsync(msg); - var botMessage = await mailChannel.SendMessageAsync(TicketResponses.MessageReceived(request.Message, ticketMessage.Attachments.ToArray())); + var botMessage = await mailChannel.SendMessageAsync(TicketBotMessages.Ticket.MessageReceived(request.Message, ticketMessage.Attachments.ToArray())); ticketMessage.BotMessageId = botMessage.Id; - var newTicketCreatedLog = LogResponses.NewTicketCreated(request.Message, mailChannel, ticket.Id); + var newTicketCreatedLog = LogBotMessages.NewTicketCreated(request.Message, mailChannel, ticket.Id); var logChannel = await _sender.Send(new GetDiscordLogChannelQuery(), cancellationToken); await logChannel.SendMessageAsync(newTicketCreatedLog); - await request.Message.CreateReactionAsync(DiscordEmoji.FromUnicode(Const.ProcessedReactionDiscordEmojiUnicode)); + await request.Message.CreateReactionAsync(DiscordEmoji.FromUnicode(TicketConstants.ProcessedReactionDiscordEmojiUnicode)); } } \ No newline at end of file diff --git a/src/Modmail.NET/Features/TicketType/Handlers/ProcessCreateTicketTypeHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateTicketTypeHandler.cs similarity index 83% rename from src/Modmail.NET/Features/TicketType/Handlers/ProcessCreateTicketTypeHandler.cs rename to src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateTicketTypeHandler.cs index 9760a619..9477c2ff 100644 --- a/src/Modmail.NET/Features/TicketType/Handlers/ProcessCreateTicketTypeHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessCreateTicketTypeHandler.cs @@ -1,9 +1,12 @@ using MediatR; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Exceptions; -using Modmail.NET.Utils; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Queries; -namespace Modmail.NET.Features.TicketType.Handlers; +namespace Modmail.NET.Features.Ticket.Handlers; public class ProcessCreateTicketTypeHandler : IRequestHandler { @@ -26,7 +29,7 @@ public async Task Handle(ProcessCreateTicketTypeCommand request, CancellationTok var id = Guid.CreateVersion7(); var idClean = id.ToString().Replace("-", ""); - var ticketType = new Entities.TicketType { + var ticketType = new TicketType { Id = id, Key = idClean, Name = request.Name, diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessModSendMessageHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessModSendMessageHandler.cs index 60ceb594..4368866e 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessModSendMessageHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessModSendMessageHandler.cs @@ -1,11 +1,15 @@ using DSharpPlus.Entities; using MediatR; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Guild; -using Modmail.NET.Services; -using Modmail.NET.Utils; +using Modmail.NET.Features.Guild.Queries; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Helpers; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Features.Ticket.Services; +using Modmail.NET.Features.Ticket.Static; +using TicketMessage = Modmail.NET.Database.Entities.TicketMessage; namespace Modmail.NET.Features.Ticket.Handlers; @@ -48,7 +52,7 @@ public async Task Handle(ProcessModSendMessageCommand request, CancellationToken var anonymous = guildOption.AlwaysAnonymous || ticket.Anonymous; var privateChannel = await _bot.Client.GetChannelAsync(ticket.PrivateMessageChannelId); - var embed = UserResponses.MessageReceived(request.Message, ticketMessage.Attachments.ToArray(), anonymous); + var embed = TicketBotMessages.User.MessageReceived(request.Message, ticketMessage.Attachments.ToArray(), anonymous); var botMessage = await privateChannel.SendMessageAsync(embed); ticketMessage.BotMessageId = botMessage.Id; @@ -57,6 +61,6 @@ public async Task Handle(ProcessModSendMessageCommand request, CancellationToken var affected = await _dbContext.SaveChangesAsync(cancellationToken); if (affected == 0) throw new DbInternalException(); - await request.Message.CreateReactionAsync(DiscordEmoji.FromUnicode(Const.ProcessedReactionDiscordEmojiUnicode)); + await request.Message.CreateReactionAsync(DiscordEmoji.FromUnicode(TicketConstants.ProcessedReactionDiscordEmojiUnicode)); } } \ No newline at end of file diff --git a/src/Modmail.NET/Features/TicketType/Handlers/ProcessRemoveTicketTypeHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessRemoveTicketTypeHandler.cs similarity index 73% rename from src/Modmail.NET/Features/TicketType/Handlers/ProcessRemoveTicketTypeHandler.cs rename to src/Modmail.NET/Features/Ticket/Handlers/ProcessRemoveTicketTypeHandler.cs index 37f90fb1..e1f36f55 100644 --- a/src/Modmail.NET/Features/TicketType/Handlers/ProcessRemoveTicketTypeHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessRemoveTicketTypeHandler.cs @@ -1,11 +1,13 @@ using MediatR; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Ticket; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Queries; -namespace Modmail.NET.Features.TicketType.Handlers; +namespace Modmail.NET.Features.Ticket.Handlers; -public class ProcessRemoveTicketTypeHandler : IRequestHandler +public class ProcessRemoveTicketTypeHandler : IRequestHandler { private readonly ModmailDbContext _dbContext; private readonly ISender _sender; @@ -16,7 +18,7 @@ public ProcessRemoveTicketTypeHandler(ModmailDbContext dbContext, _sender = sender; } - public async Task Handle(ProcessRemoveTicketTypeCommand request, CancellationToken cancellationToken) { + public async Task Handle(ProcessRemoveTicketTypeCommand request, CancellationToken cancellationToken) { var ticketType = await _sender.Send(new GetTicketTypeQuery(request.Id), cancellationToken); var allTicketsByType = await _sender.Send(new GetTicketListByTypeQuery(ticketType.Id), cancellationToken); diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessToggleAnonymousHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessToggleAnonymousHandler.cs index 70756730..616ba997 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessToggleAnonymousHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessToggleAnonymousHandler.cs @@ -1,6 +1,9 @@ using MediatR; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Helpers; +using Modmail.NET.Features.Ticket.Queries; namespace Modmail.NET.Features.Ticket.Handlers; @@ -30,7 +33,7 @@ public async Task Handle(ProcessToggleAnonymousCommand request, CancellationToke _ = Task.Run(async () => { var ticketChannel = request.TicketChannel ?? await _bot.Client.GetChannelAsync(ticket.ModMessageChannelId); - if (ticketChannel is not null) await ticketChannel.SendMessageAsync(TicketResponses.AnonymousToggled(ticket)); + if (ticketChannel is not null) await ticketChannel.SendMessageAsync(TicketBotMessages.Ticket.AnonymousToggled(ticket)); }, cancellationToken); } } \ No newline at end of file diff --git a/src/Modmail.NET/Features/TicketType/Handlers/ProcessUpdateTicketTypeHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessUpdateTicketTypeHandler.cs similarity index 89% rename from src/Modmail.NET/Features/TicketType/Handlers/ProcessUpdateTicketTypeHandler.cs rename to src/Modmail.NET/Features/Ticket/Handlers/ProcessUpdateTicketTypeHandler.cs index 06767c5d..7cfe1458 100644 --- a/src/Modmail.NET/Features/TicketType/Handlers/ProcessUpdateTicketTypeHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessUpdateTicketTypeHandler.cs @@ -1,8 +1,9 @@ using MediatR; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Exceptions; +using Modmail.NET.Features.Ticket.Commands; -namespace Modmail.NET.Features.TicketType.Handlers; +namespace Modmail.NET.Features.Ticket.Handlers; public class ProcessUpdateTicketTypeHandler : IRequestHandler { diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessUserSentMessageHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessUserSentMessageHandler.cs index 53d13cf1..d878cfc5 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessUserSentMessageHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessUserSentMessageHandler.cs @@ -1,11 +1,15 @@ using DSharpPlus.Entities; using MediatR; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Permission; -using Modmail.NET.Services; -using Modmail.NET.Utils; +using Modmail.NET.Features.Permission.Queries; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Helpers; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Features.Ticket.Services; +using Modmail.NET.Features.Ticket.Static; +using TicketMessage = Modmail.NET.Database.Entities.TicketMessage; namespace Modmail.NET.Features.Ticket.Handlers; @@ -44,7 +48,7 @@ public async Task Handle(ProcessUserSentMessageCommand request, CancellationToke var permissions = await _sender.Send(new GetPermissionInfoQuery(), cancellationToken); var pingOnNewTicket = permissions.Where(x => x.PingOnNewMessage).ToArray(); - var msg = TicketResponses.MessageReceived(request.Message, ticketMessage.Attachments.ToArray()); + var msg = TicketBotMessages.Ticket.MessageReceived(request.Message, ticketMessage.Attachments.ToArray()); msg.WithContent(UtilMention.GetMentionsMessageString(pingOnNewTicket)); var botMessage = await mailChannel.SendMessageAsync(msg); @@ -53,6 +57,6 @@ public async Task Handle(ProcessUserSentMessageCommand request, CancellationToke var affected = await _dbContext.SaveChangesAsync(cancellationToken); if (affected == 0) throw new DbInternalException(); - await request.Message.CreateReactionAsync(DiscordEmoji.FromUnicode(Const.ProcessedReactionDiscordEmojiUnicode)); + await request.Message.CreateReactionAsync(DiscordEmoji.FromUnicode(TicketConstants.ProcessedReactionDiscordEmojiUnicode)); } } \ No newline at end of file diff --git a/src/Modmail.NET/Static/LogResponses.cs b/src/Modmail.NET/Features/Ticket/Helpers/LogBotMessages.cs similarity index 80% rename from src/Modmail.NET/Static/LogResponses.cs rename to src/Modmail.NET/Features/Ticket/Helpers/LogBotMessages.cs index 289f00c0..7647e98a 100644 --- a/src/Modmail.NET/Static/LogResponses.cs +++ b/src/Modmail.NET/Features/Ticket/Helpers/LogBotMessages.cs @@ -1,13 +1,16 @@ using DSharpPlus.Entities; -using Modmail.NET.Entities; -using Modmail.NET.Extensions; +using Modmail.NET.Common.Extensions; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Ticket.Static; +using Modmail.NET.Language; -namespace Modmail.NET.Static; +namespace Modmail.NET.Features.Ticket.Helpers; /// /// Contains the embed messages bot to send to log channel /// -public static class LogResponses +public static class LogBotMessages { public static DiscordEmbedBuilder NewTicketCreated(DiscordMessage initialMessage, DiscordChannel mailChannel, @@ -16,17 +19,17 @@ public static DiscordEmbedBuilder NewTicketCreated(DiscordMessage initialMessage .WithTitle(LangKeys.NewTicketCreated.GetTranslation()) .WithUserAsAuthor(initialMessage.Author) .WithCustomTimestamp() - .WithColor(Colors.TicketCreatedColor) + .WithColor(ModmailColors.TicketCreatedColor) .AddField(LangKeys.TicketId.GetTranslation(), ticketId.ToString().ToUpper()) .AddField(LangKeys.User.GetTranslation(), initialMessage.Author!.Mention); return embed; } - public static DiscordEmbedBuilder TicketClosed(Ticket ticket) { + public static DiscordEmbedBuilder TicketClosed(Database.Entities.Ticket ticket) { var embed = new DiscordEmbedBuilder() .WithCustomTimestamp() .WithTitle(LangKeys.TicketClosed.GetTranslation()) - .WithColor(Colors.TicketClosedColor) + .WithColor(ModmailColors.TicketClosedColor) .AddField(LangKeys.TicketId.GetTranslation(), ticket.Id.ToString().ToUpper()) .AddField(LangKeys.OpenedBy.GetTranslation(), ticket.OpenerUser!.GetMention(), true) .AddField(LangKeys.ClosedBy.GetTranslation(), ticket.CloserUser!.GetMention(), true) @@ -42,7 +45,7 @@ public static DiscordEmbedBuilder BlacklistAdded(DiscordUserInfo author, Discord var embed = new DiscordEmbedBuilder() .WithTitle(LangKeys.UserBlacklisted.GetTranslation()) .WithUserAsAuthor(author) - .WithColor(Colors.InfoColor) + .WithColor(ModmailColors.InfoColor) .AddField(LangKeys.User.GetTranslation(), user.GetMention(), true) .AddField(LangKeys.UserId.GetTranslation(), user.Id.ToString(), true); @@ -54,18 +57,18 @@ public static DiscordEmbedBuilder BlacklistRemoved(DiscordUserInfo author, Disco var embed = new DiscordEmbedBuilder() .WithTitle(LangKeys.UserBlacklistRemoved.GetTranslation()) .WithUserAsAuthor(author) - .WithColor(Colors.InfoColor) + .WithColor(ModmailColors.InfoColor) .AddField(LangKeys.User.GetTranslation(), user.GetMention(), true) .AddField(LangKeys.UserId.GetTranslation(), user.Id.ToString(), true); return embed; } - public static DiscordEmbedBuilder TicketPriorityChanged(DiscordUserInfo modUser, Ticket ticket, TicketPriority oldPriority, TicketPriority newPriority) { + public static DiscordEmbedBuilder TicketPriorityChanged(DiscordUserInfo modUser, Database.Entities.Ticket ticket, TicketPriority oldPriority, TicketPriority newPriority) { var embed = new DiscordEmbedBuilder() .WithTitle(LangKeys.TicketPriorityChanged.GetTranslation()) .WithCustomTimestamp() - .WithColor(Colors.TicketPriorityChangedColor) + .WithColor(ModmailColors.TicketPriorityChangedColor) .AddField(LangKeys.TicketId.GetTranslation(), ticket.Id.ToString().ToUpper()) .AddField(LangKeys.OldPriority.GetTranslation(), oldPriority.ToString(), true) .AddField(LangKeys.NewPriority.GetTranslation(), newPriority.ToString(), true); diff --git a/src/Modmail.NET/Features/Ticket/Helpers/TicketBotMessages.cs b/src/Modmail.NET/Features/Ticket/Helpers/TicketBotMessages.cs new file mode 100644 index 00000000..41fcedc1 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Helpers/TicketBotMessages.cs @@ -0,0 +1,236 @@ +using DSharpPlus.Entities; +using Modmail.NET.Common.Extensions; +using Modmail.NET.Common.Static; +using Modmail.NET.Common.Utils; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Ticket.Static; +using Modmail.NET.Language; + +namespace Modmail.NET.Features.Ticket.Helpers; + +public static class TicketBotMessages +{ + public static class User + { + public static DiscordEmbedBuilder FeedbackReceivedUpdateMessage(Database.Entities.Ticket ticket) { + var feedbackDone = new DiscordEmbedBuilder() + .WithTitle(LangKeys.FeedbackReceived.GetTranslation()) + .WithCustomTimestamp() + .WithGuildInfoFooter() + .AddField(LangKeys.Star.GetTranslation(), LangKeys.StarEmoji.GetTranslation() + ticket.FeedbackStar) + .AddField(LangKeys.Feedback.GetTranslation(), ticket.FeedbackMessage) + .WithColor(ModmailColors.FeedbackColor); + return feedbackDone; + } + + + public static DiscordMessageBuilder YourTicketHasBeenClosed(Database.Entities.Ticket ticket, GuildOption guildOption, Uri transcriptUri) { + var messageBuilder = new DiscordMessageBuilder(); + var embedBuilder = new DiscordEmbedBuilder() + .WithTitle(LangKeys.YourTicketHasBeenClosed.GetTranslation()) + .WithDescription(LangKeys.YourTicketHasBeenClosedDescription.GetTranslation()) + .WithGuildInfoFooter(guildOption) + .WithCustomTimestamp() + .WithColor(ModmailColors.TicketClosedColor); + + var closingMessage = LangKeys.ClosingMessageDescription.GetTranslation(); + + if (!string.IsNullOrEmpty(closingMessage)) embedBuilder.WithDescription(closingMessage); + + if (!string.IsNullOrEmpty(ticket.CloseReason)) embedBuilder.AddField(LangKeys.CloseReason.GetTranslation(), ticket.CloseReason); + + if (transcriptUri is not null) messageBuilder.AddComponents(new DiscordLinkButtonComponent(transcriptUri.AbsoluteUri, LangKeys.Transcript.GetTranslation())); + + messageBuilder.AddEmbed(embedBuilder); + return messageBuilder; + } + + public static DiscordMessageBuilder GiveFeedbackMessage(Database.Entities.Ticket ticket, GuildOption guildOption) { + var ticketFeedbackMsgToUser = new DiscordMessageBuilder(); + var starList = new List { + new DiscordButtonComponent(DiscordButtonStyle.Primary, UtilInteraction.BuildKey("star", 1, ticket.Id), "1", false, new DiscordComponentEmoji("⭐")), + new DiscordButtonComponent(DiscordButtonStyle.Primary, UtilInteraction.BuildKey("star", 2, ticket.Id), "2", false, new DiscordComponentEmoji("⭐")), + new DiscordButtonComponent(DiscordButtonStyle.Primary, UtilInteraction.BuildKey("star", 3, ticket.Id), "3", false, new DiscordComponentEmoji("⭐")), + new DiscordButtonComponent(DiscordButtonStyle.Primary, UtilInteraction.BuildKey("star", 4, ticket.Id), "4", false, new DiscordComponentEmoji("⭐")), + new DiscordButtonComponent(DiscordButtonStyle.Primary, UtilInteraction.BuildKey("star", 5, ticket.Id), "5", false, new DiscordComponentEmoji("⭐")) + }; + + var ticketFeedbackEmbed = new DiscordEmbedBuilder() + .WithTitle(LangKeys.Feedback.GetTranslation()) + .WithDescription(LangKeys.FeedbackDescription.GetTranslation()) + .WithCustomTimestamp() + .WithGuildInfoFooter(guildOption) + .WithColor(ModmailColors.FeedbackColor); + + var response = ticketFeedbackMsgToUser + .AddEmbed(ticketFeedbackEmbed) + .AddComponents(starList); + return response; + } + + public static DiscordEmbedBuilder TicketPriorityChanged(GuildOption guildOption, DiscordUserInfo info, Database.Entities.Ticket ticket, TicketPriority oldPriority, TicketPriority newPriority) { + var embed = new DiscordEmbedBuilder() + .WithGuildInfoFooter(guildOption) + .WithTitle(LangKeys.TicketPriorityChanged.GetTranslation()) + .WithCustomTimestamp() + .WithColor(ModmailColors.TicketPriorityChangedColor) + .AddField(LangKeys.OldPriority.GetTranslation(), oldPriority.ToString(), true) + .AddField(LangKeys.NewPriority.GetTranslation(), newPriority.ToString(), true); + if (!ticket.Anonymous) embed.WithUserAsAuthor(info); + // else embed.WithUserAsAuthor(ModmailBot.This.Client.CurrentUser); + return embed; + } + + + public static DiscordMessageBuilder YouHaveCreatedNewTicket(DiscordGuild guild, + GuildOption option, + List ticketTypes, + Guid ticketId) { + var embed = new DiscordEmbedBuilder() + .WithTitle(LangKeys.YouHaveCreatedNewTicket.GetTranslation()) + .WithFooter(guild.Name, guild.IconUrl) + .WithCustomTimestamp() + .WithColor(ModmailColors.TicketCreatedColor); + var greetingMessage = LangKeys.GreetingMessageDescription.GetTranslation(); + if (!string.IsNullOrEmpty(greetingMessage)) + embed.WithDescription(greetingMessage); + + var builder = new DiscordMessageBuilder() + .AddEmbed(embed); + + if (ticketTypes.Count > 0) { + var selectBox = new DiscordSelectComponent(UtilInteraction.BuildKey("ticket_type", ticketId.ToString()), + LangKeys.PleaseSelectATicketType.GetTranslation(), + ticketTypes.Select(x => new DiscordSelectComponentOption(x.Name, + x.Key.ToString(), + x.Description, + false, + !string.IsNullOrWhiteSpace(x.Emoji) + ? new DiscordComponentEmoji(x.Emoji) + : null)) + .ToList()); + builder.AddComponents(selectBox); + } + + return builder; + } + + + public static DiscordMessageBuilder MessageReceived(DiscordMessage message, TicketMessageAttachment[] attachments, bool anonymous) { + var embed = new DiscordEmbedBuilder() + .WithDescription(message.Content) + .WithGuildInfoFooter() + .WithCustomTimestamp() + .WithColor(ModmailColors.MessageReceivedColor); + + if (!anonymous) embed.WithUserAsAuthor(message.Author); + + var msg = new DiscordMessageBuilder(); + + msg.AddEmbed(embed); + msg.AddAttachments(attachments); + + return msg; + } + } + + public static class Ticket + { + public static DiscordMessageBuilder NewTicket(DiscordUser member, Guid ticketId) { + var embed = new DiscordEmbedBuilder() + .WithTitle(LangKeys.NewTicket.GetTranslation()) + .WithCustomTimestamp() + .WithDescription(LangKeys.NewTicketDescriptionMessage.GetTranslation()) + .WithAuthor(member.GetUsername(), iconUrl: member.AvatarUrl) + .AddField(LangKeys.User.GetTranslation(), member.Mention, true) + .AddField(LangKeys.TicketId.GetTranslation(), ticketId.ToString().ToUpper(), true) + .WithColor(ModmailColors.TicketCreatedColor); + + var messageBuilder = new DiscordMessageBuilder() + .AddEmbed(embed) + .AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Danger, + UtilInteraction.BuildKey("close_ticket_with_reason", ticketId.ToString()), + LangKeys.CloseTicket.GetTranslation(), + emoji: new DiscordComponentEmoji("🔒")) + ); + return messageBuilder; + } + + public static DiscordEmbedBuilder NoteAdded(TicketNote note, DiscordUserInfo user) { + var embed = new DiscordEmbedBuilder() + .WithTitle(LangKeys.NoteAdded.GetTranslation()) + .WithDescription(note.Content) + .WithColor(ModmailColors.NoteAddedColor) + .WithCustomTimestamp() + .WithUserAsAuthor(user); + return embed; + } + + public static DiscordEmbedBuilder AnonymousToggled(Database.Entities.Ticket ticket) { + var embed2 = new DiscordEmbedBuilder() + .WithTitle(ticket.Anonymous + ? LangKeys.AnonymousModOn.GetTranslation() + : LangKeys.AnonymousModOff.GetTranslation()) + .WithColor(ModmailColors.AnonymousToggledColor) + .WithCustomTimestamp() + .WithDescription(ticket.Anonymous + ? LangKeys.TicketSetAnonymousDescription.GetTranslation() + : LangKeys.TicketSetNotAnonymousDescription.GetTranslation()); + + if (ticket.OpenerUser is not null) embed2.WithUserAsAuthor(ticket.OpenerUser); + + + return embed2; + } + + public static DiscordEmbedBuilder TicketTypeChanged(DiscordUserInfo user, TicketType ticketType) { + var embed = new DiscordEmbedBuilder() + .WithTitle(LangKeys.TicketTypeChanged.GetTranslation()) + .WithUserAsAuthor(user) + .WithCustomTimestamp() + .WithColor(ModmailColors.TicketTypeChangedColor); + if (ticketType is not null) + embed.WithDescription(string.Format(LangKeys.TicketTypeSet.GetTranslation(), ticketType.Emoji, ticketType.Name)); + else + embed.WithDescription(LangKeys.TicketTypeRemoved.GetTranslation()); + + return embed; + } + + public static DiscordEmbedBuilder TicketPriorityChanged(DiscordUserInfo modUser, Database.Entities.Ticket ticket, TicketPriority oldPriority, TicketPriority newPriority) { + var embed = new DiscordEmbedBuilder() + .WithTitle(LangKeys.TicketPriorityChanged.GetTranslation()) + .WithColor(ModmailColors.TicketPriorityChangedColor) + .WithCustomTimestamp() + .AddField(LangKeys.OldPriority.GetTranslation(), oldPriority.ToString(), true) + .AddField(LangKeys.NewPriority.GetTranslation(), newPriority.ToString(), true) + .WithUserAsAuthor(modUser); + return embed; + } + + public static DiscordMessageBuilder MessageReceived(DiscordMessage message, + TicketMessageAttachment[] attachments) { + var embed = new DiscordEmbedBuilder() + .WithDescription(message.Content) + .WithCustomTimestamp() + .WithColor(ModmailColors.MessageReceivedColor) + .WithUserAsAuthor(message.Author); + + var msgBuilder = new DiscordMessageBuilder() + .AddEmbed(embed) + .AddAttachments(attachments); + return msgBuilder; + } + + public static DiscordEmbedBuilder MessageEdited(DiscordMessage message) { + var embed = new DiscordEmbedBuilder() + .WithDescription(message.Content) + .WithCustomTimestamp() + .WithColor(ModmailColors.MessageReceivedColor) + .WithFooter(LangKeys.Edited.GetTranslation()) + .WithUserAsAuthor(message.Author); + + return embed; + } + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Static/Modals.cs b/src/Modmail.NET/Features/Ticket/Helpers/TicketModals.cs similarity index 92% rename from src/Modmail.NET/Static/Modals.cs rename to src/Modmail.NET/Features/Ticket/Helpers/TicketModals.cs index b3e0d994..f2d2e451 100644 --- a/src/Modmail.NET/Static/Modals.cs +++ b/src/Modmail.NET/Features/Ticket/Helpers/TicketModals.cs @@ -1,9 +1,10 @@ using DSharpPlus.Entities; -using Modmail.NET.Utils; +using Modmail.NET.Common.Utils; +using Modmail.NET.Language; -namespace Modmail.NET.Static; +namespace Modmail.NET.Features.Ticket.Helpers; -public static class Modals +public static class TicketModals { public static DiscordInteractionResponseBuilder CreateFeedbackModal(int starCount, Guid ticketId, ulong messageId) { var modal = new DiscordInteractionResponseBuilder() diff --git a/src/Modmail.NET/Jobs/TicketDataDeleteJob.cs b/src/Modmail.NET/Features/Ticket/Jobs/TicketDataDeleteJob.cs similarity index 95% rename from src/Modmail.NET/Jobs/TicketDataDeleteJob.cs rename to src/Modmail.NET/Features/Ticket/Jobs/TicketDataDeleteJob.cs index 4cce73e4..4a5026b1 100644 --- a/src/Modmail.NET/Jobs/TicketDataDeleteJob.cs +++ b/src/Modmail.NET/Features/Ticket/Jobs/TicketDataDeleteJob.cs @@ -3,12 +3,12 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Modmail.NET.Abstract; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Features.Guild; -using Modmail.NET.Utils; +using Modmail.NET.Features.Guild.Queries; using Serilog; -namespace Modmail.NET.Jobs; +namespace Modmail.NET.Features.Ticket.Jobs; public class TicketDataDeleteJob : HangfireRecurringJobBase { diff --git a/src/Modmail.NET/Jobs/TicketTimeoutJob.cs b/src/Modmail.NET/Features/Ticket/Jobs/TicketTimeoutJob.cs similarity index 86% rename from src/Modmail.NET/Jobs/TicketTimeoutJob.cs rename to src/Modmail.NET/Features/Ticket/Jobs/TicketTimeoutJob.cs index fce02f3b..cbc0674a 100644 --- a/src/Modmail.NET/Jobs/TicketTimeoutJob.cs +++ b/src/Modmail.NET/Features/Ticket/Jobs/TicketTimeoutJob.cs @@ -3,14 +3,13 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Modmail.NET.Abstract; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Features.Guild; -using Modmail.NET.Features.Ticket; -using Modmail.NET.Utils; +using Modmail.NET.Features.Guild.Queries; +using Modmail.NET.Features.Ticket.Commands; using Serilog; -namespace Modmail.NET.Jobs; +namespace Modmail.NET.Features.Ticket.Jobs; public class TicketTimeoutJob : HangfireRecurringJobBase { @@ -30,16 +29,15 @@ public override async Task Execute() { if (guildOption.TicketTimeoutHours == -1) return; //Disabled var tickets = await GetTimeoutTickets(); - if (tickets.Length > 0) { + if (tickets.Length > 0) foreach (var ticket in tickets) { await sender.Send(new ProcessCloseTicketCommand(ticket.Id, bot.Client.CurrentUser.Id, "Ticket timed out")); Log.Information("Ticket {TicketId} has been closed due to timeout", ticket.Id); } - } return; - async Task GetTimeoutTickets() { + async Task GetTimeoutTickets() { if (guildOption.TicketTimeoutHours == -1) return []; var timeoutDate = UtilDate.GetNow().AddHours(-guildOption.TicketTimeoutHours); diff --git a/src/Modmail.NET/Jobs/TicketTypeSelectionTimeoutJob.cs b/src/Modmail.NET/Features/Ticket/Jobs/TicketTypeSelectionTimeoutJob.cs similarity index 94% rename from src/Modmail.NET/Jobs/TicketTypeSelectionTimeoutJob.cs rename to src/Modmail.NET/Features/Ticket/Jobs/TicketTypeSelectionTimeoutJob.cs index 77f55c94..46362d62 100644 --- a/src/Modmail.NET/Jobs/TicketTypeSelectionTimeoutJob.cs +++ b/src/Modmail.NET/Features/Ticket/Jobs/TicketTypeSelectionTimeoutJob.cs @@ -2,9 +2,9 @@ using DSharpPlus.Entities; using Hangfire; using Modmail.NET.Abstract; -using Modmail.NET.Utils; +using Modmail.NET.Common.Utils; -namespace Modmail.NET.Jobs; +namespace Modmail.NET.Features.Ticket.Jobs; public class TicketTypeSelectionTimeoutJob : HangfireRecurringJobBase { diff --git a/src/Modmail.NET/Features/Ticket/Mappers/TicketDtoMapper.cs b/src/Modmail.NET/Features/Ticket/Mappers/TicketDtoMapper.cs new file mode 100644 index 00000000..a07ba912 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Mappers/TicketDtoMapper.cs @@ -0,0 +1,12 @@ +using Modmail.NET.Features.Ticket.Models; +using Riok.Mapperly.Abstractions; + +namespace Modmail.NET.Features.Ticket.Mappers; + +[Mapper] +public static partial class TicketDtoMapper +{ + public static partial IQueryable ProjectToDto(this IQueryable queryable); + + public static partial TicketDto ToDto(this Database.Entities.Ticket entity); +} \ No newline at end of file diff --git a/src/Modmail.NET/Models/Dto/TicketDto.cs b/src/Modmail.NET/Features/Ticket/Models/TicketDto.cs similarity index 87% rename from src/Modmail.NET/Models/Dto/TicketDto.cs rename to src/Modmail.NET/Features/Ticket/Models/TicketDto.cs index 6768ee69..61d8d1ce 100644 --- a/src/Modmail.NET/Models/Dto/TicketDto.cs +++ b/src/Modmail.NET/Features/Ticket/Models/TicketDto.cs @@ -1,6 +1,6 @@ -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; -namespace Modmail.NET.Models.Dto; +namespace Modmail.NET.Features.Ticket.Models; public class TicketDto { diff --git a/src/Modmail.NET/Features/Ticket/Queries.cs b/src/Modmail.NET/Features/Ticket/Queries.cs deleted file mode 100644 index 8b4264fb..00000000 --- a/src/Modmail.NET/Features/Ticket/Queries.cs +++ /dev/null @@ -1,17 +0,0 @@ -using MediatR; - -namespace Modmail.NET.Features.Ticket; - -public sealed record GetTicketQuery( - Guid Id, - bool AllowNull = false, - bool MustBeOpen = false, - bool MustBeClosed = false) : IRequest; - -public sealed record GetTicketByUserIdQuery( - ulong UserId, - bool AllowNull = false, - bool MustBeOpen = false, - bool MustBeClosed = false) : IRequest; - -public sealed record GetTicketListByTypeQuery(Guid TicketTypeId) : IRequest>; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Queries/CheckTicketTypeExistsQuery.cs b/src/Modmail.NET/Features/Ticket/Queries/CheckTicketTypeExistsQuery.cs new file mode 100644 index 00000000..6b704221 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Queries/CheckTicketTypeExistsQuery.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace Modmail.NET.Features.Ticket.Queries; + +public sealed record CheckTicketTypeExistsQuery(string NameOrKey) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Queries/GetTicketByUserIdQuery.cs b/src/Modmail.NET/Features/Ticket/Queries/GetTicketByUserIdQuery.cs new file mode 100644 index 00000000..f82325c5 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Queries/GetTicketByUserIdQuery.cs @@ -0,0 +1,9 @@ +using MediatR; + +namespace Modmail.NET.Features.Ticket.Queries; + +public sealed record GetTicketByUserIdQuery( + ulong UserId, + bool AllowNull = false, + bool MustBeOpen = false, + bool MustBeClosed = false) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Queries/GetTicketListByTypeQuery.cs b/src/Modmail.NET/Features/Ticket/Queries/GetTicketListByTypeQuery.cs new file mode 100644 index 00000000..7fb64e10 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Queries/GetTicketListByTypeQuery.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace Modmail.NET.Features.Ticket.Queries; + +public sealed record GetTicketListByTypeQuery(Guid TicketTypeId) : IRequest>; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Queries/GetTicketQuery.cs b/src/Modmail.NET/Features/Ticket/Queries/GetTicketQuery.cs new file mode 100644 index 00000000..f08a295a --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Queries/GetTicketQuery.cs @@ -0,0 +1,9 @@ +using MediatR; + +namespace Modmail.NET.Features.Ticket.Queries; + +public sealed record GetTicketQuery( + Guid Id, + bool AllowNull = false, + bool MustBeOpen = false, + bool MustBeClosed = false) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeByChannelIdQuery.cs b/src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeByChannelIdQuery.cs new file mode 100644 index 00000000..5a94c757 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeByChannelIdQuery.cs @@ -0,0 +1,8 @@ +using MediatR; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Ticket.Queries; + +public sealed record GetTicketTypeByChannelIdQuery( + ulong ChannelId, + bool AllowNull = false) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeBySearchQuery.cs b/src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeBySearchQuery.cs new file mode 100644 index 00000000..488ee20c --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeBySearchQuery.cs @@ -0,0 +1,8 @@ +using MediatR; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Ticket.Queries; + +public sealed record GetTicketTypeBySearchQuery( + string NameOrKey, + bool AllowNull = false) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeListQuery.cs b/src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeListQuery.cs new file mode 100644 index 00000000..bf284d0d --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeListQuery.cs @@ -0,0 +1,6 @@ +using MediatR; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Ticket.Queries; + +public sealed record GetTicketTypeListQuery(bool OnlyActive = false) : IRequest>; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeQuery.cs b/src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeQuery.cs new file mode 100644 index 00000000..a33dca61 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Queries/GetTicketTypeQuery.cs @@ -0,0 +1,8 @@ +using MediatR; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Ticket.Queries; + +public sealed record GetTicketTypeQuery( + Guid Id, + bool AllowNull = false) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Services/TicketAttachmentDownloadService.cs b/src/Modmail.NET/Features/Ticket/Services/TicketAttachmentDownloadService.cs similarity index 73% rename from src/Modmail.NET/Services/TicketAttachmentDownloadService.cs rename to src/Modmail.NET/Features/Ticket/Services/TicketAttachmentDownloadService.cs index 60934d0f..e7a5e15c 100644 --- a/src/Modmail.NET/Services/TicketAttachmentDownloadService.cs +++ b/src/Modmail.NET/Features/Ticket/Services/TicketAttachmentDownloadService.cs @@ -1,9 +1,13 @@ +using Modmail.NET.Common.Static; using Serilog; -namespace Modmail.NET.Services; +namespace Modmail.NET.Features.Ticket.Services; public class TicketAttachmentDownloadService { + public const string AttachmentDownloadDirectory = "AttachmentDownloads"; + public const int HttpClientDownloadTimeoutSeconds = 90; + private readonly IHttpClientFactory _httpClientFactory; public TicketAttachmentDownloadService(IHttpClientFactory httpClientFactory) { @@ -13,13 +17,13 @@ public TicketAttachmentDownloadService(IHttpClientFactory httpClientFactory) { public async Task Handle(Guid attachmentId, string url, string fileExtension) { if (attachmentId == Guid.Empty) throw new ArgumentNullException(nameof(attachmentId)); - if (!Directory.Exists(Const.AttachmentDownloadDirectory)) Directory.CreateDirectory(Const.AttachmentDownloadDirectory); + if (!Directory.Exists(AttachmentDownloadDirectory)) Directory.CreateDirectory(AttachmentDownloadDirectory); - var filePath = Path.Combine(Const.AttachmentDownloadDirectory, $"{attachmentId}.{fileExtension.Trim().Trim('.')}"); + var filePath = Path.Combine(AttachmentDownloadDirectory, $"{attachmentId}.{fileExtension.Trim().Trim('.')}"); if (Path.Exists(filePath)) throw new InvalidOperationException("File in path already exists: " + filePath); var client = _httpClientFactory.CreateClient(); - client.Timeout = TimeSpan.FromSeconds(Const.HttpClientDownloadTimeoutSeconds); + client.Timeout = TimeSpan.FromSeconds(HttpClientDownloadTimeoutSeconds); try { using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead)) { diff --git a/src/Modmail.NET/Queues/TicketMessageQueue.cs b/src/Modmail.NET/Features/Ticket/Services/TicketMessage.cs similarity index 77% rename from src/Modmail.NET/Queues/TicketMessageQueue.cs rename to src/Modmail.NET/Features/Ticket/Services/TicketMessage.cs index a3f87f0e..b7befe5c 100644 --- a/src/Modmail.NET/Queues/TicketMessageQueue.cs +++ b/src/Modmail.NET/Features/Ticket/Services/TicketMessage.cs @@ -4,20 +4,23 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Modmail.NET.Abstract; -using Modmail.NET.Aspects; -using Modmail.NET.Features.Blacklist; -using Modmail.NET.Features.Ticket; -using Modmail.NET.Utils; +using Modmail.NET.Common.Aspects; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; +using Modmail.NET.Features.Blacklist.Queries; +using Modmail.NET.Features.Blacklist.Static; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Queries; using Serilog; -namespace Modmail.NET.Queues; +namespace Modmail.NET.Features.Ticket.Services; -public class TicketMessageQueue : BaseQueue +public class TicketMessage : MemoryQueueBase { private readonly IOptions _options; private readonly IServiceScopeFactory _scopeFactory; - public TicketMessageQueue( + public TicketMessage( IServiceScopeFactory scopeFactory, IOptions options ) : base(TimeSpan.FromMinutes(15)) { @@ -29,7 +32,7 @@ protected override async Task Handle(ulong userId, MessageCreatedEventArgs args) if (args.Message.Content.StartsWith(_options.Value.BotPrefix)) { Log.Debug( "[{Source}] Ignoring message due to bot prefix. UserId: {UserId}, Message: {Message}", - nameof(TicketMessageQueue), + nameof(TicketMessage), userId, args.Message.Content ); @@ -50,7 +53,7 @@ DiscordUser user ) { Log.Debug( "[{Source}] Handling private ticket message. UserId: {UserId}, Message: {Message}", - nameof(TicketMessageQueue), + nameof(TicketMessage), user.Id, message.Content ); @@ -62,10 +65,10 @@ DiscordUser user if (await sender.Send(new CheckUserBlacklistStatusQuery(user.Id))) { Log.Information( "[{Source}] User is blacklisted, sending rejection message. UserId: {UserId}", - nameof(TicketMessageQueue), + nameof(TicketMessage), user.Id ); - await channel.SendMessageAsync(UserResponses.YouHaveBeenBlacklisted()); + await channel.SendMessageAsync(BlacklistBotMessages.YouHaveBeenBlacklisted()); return; } @@ -73,7 +76,7 @@ DiscordUser user if (activeTicket is not null) { Log.Debug( "[{Source}] Active ticket found, processing user message. TicketId: {TicketId}, UserId: {UserId}", - nameof(TicketMessageQueue), + nameof(TicketMessage), activeTicket.Id, user.Id ); @@ -82,7 +85,7 @@ DiscordUser user else { Log.Debug( "[{Source}] No active ticket found, creating a new ticket. UserId: {UserId}", - nameof(TicketMessageQueue), + nameof(TicketMessage), user.Id ); await sender.Send(new ProcessCreateNewTicketCommand(user, channel, message)); @@ -90,16 +93,16 @@ DiscordUser user Log.Information( "[{Source}] Processed private message. UserId: {UserId}, Message: {Message}", - nameof(TicketMessageQueue), + nameof(TicketMessage), user.Id, message.Content ); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning( ex, - "[{Source}] BotExceptionBase: Error processing private message. UserId: {UserId}, Message: {Message}", - nameof(TicketMessageQueue), + "[{Source}] ModmailBotException: Error processing private message. UserId: {UserId}, Message: {Message}", + nameof(TicketMessage), user.Id, message.Content ); @@ -108,7 +111,7 @@ DiscordUser user Log.Error( ex, "[{Source}] Unexpected error processing private message. UserId: {UserId}, Message: {Message}", - nameof(TicketMessageQueue), + nameof(TicketMessage), user.Id, message.Content ); @@ -124,7 +127,7 @@ DiscordGuild guild ) { Log.Debug( "[{Source}] Handling guild ticket message. ChannelId: {ChannelId}, UserId: {UserId}, Message: {Message}", - nameof(TicketMessageQueue), + nameof(TicketMessage), channel.Id, modUser.Id, message.Content @@ -135,7 +138,7 @@ DiscordGuild guild if (ticketId == Guid.Empty) { Log.Warning( "[{Source}] Invalid ticket id, ignoring message. ChannelId: {ChannelId}, Topic: {Topic}", - nameof(TicketMessageQueue), + nameof(TicketMessage), channel.Id, channel.Topic ); @@ -147,17 +150,17 @@ DiscordGuild guild await sender.Send(new ProcessModSendMessageCommand(ticketId, modUser, message, channel, guild)); Log.Information( "[{Source}] Processed guild message. TicketId: {TicketId}, UserId: {UserId}, Message: {Message}", - nameof(TicketMessageQueue), + nameof(TicketMessage), ticketId, modUser.Id, message.Content ); } - catch (BotExceptionBase ex) { + catch (ModmailBotException ex) { Log.Warning( ex, - "[{Source}] BotExceptionBase: Error processing guild message. TicketId: {TicketId}, UserId: {UserId}, Message: {Message}", - nameof(TicketMessageQueue), + "[{Source}] ModmailBotException: Error processing guild message. TicketId: {TicketId}, UserId: {UserId}, Message: {Message}", + nameof(TicketMessage), ticketId, modUser.Id, message.Content @@ -167,7 +170,7 @@ DiscordGuild guild Log.Error( ex, "[{Source}] Unexpected error processing guild message. TicketId: {TicketId}, UserId: {UserId}, Message: {Message}", - nameof(TicketMessageQueue), + nameof(TicketMessage), ticketId, modUser.Id, message.Content diff --git a/src/Modmail.NET/Features/Ticket/Static/TicketConstants.cs b/src/Modmail.NET/Features/Ticket/Static/TicketConstants.cs new file mode 100644 index 00000000..96c04f3d --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Static/TicketConstants.cs @@ -0,0 +1,17 @@ +namespace Modmail.NET.Features.Ticket.Static; + +public static class TicketConstants +{ + public const string TicketNameTemplate = "ticket-{0}"; + public const string HighPriorityEmoji = "🔴"; + public const string NormalPriorityEmoji = ""; + public const string LowPriorityEmoji = "🟢"; + + public const int TicketTimeoutMinAllowedHours = 12; + public const int TicketTimeoutMaxAllowedHours = 24 * 7 * 4; + + public const int TicketDataDeleteWaitDaysMin = 1; + public const int TicketDataDeleteWaitDaysMax = 365; + + public const string ProcessedReactionDiscordEmojiUnicode = "✅"; +} \ No newline at end of file diff --git a/src/Modmail.NET/Static/TicketPriority.cs b/src/Modmail.NET/Features/Ticket/Static/TicketPriority.cs similarity index 82% rename from src/Modmail.NET/Static/TicketPriority.cs rename to src/Modmail.NET/Features/Ticket/Static/TicketPriority.cs index e83f0408..7739c3ec 100644 --- a/src/Modmail.NET/Static/TicketPriority.cs +++ b/src/Modmail.NET/Features/Ticket/Static/TicketPriority.cs @@ -1,6 +1,6 @@ using DSharpPlus.Commands.Processors.SlashCommands.ArgumentModifiers; -namespace Modmail.NET.Static; +namespace Modmail.NET.Features.Ticket.Static; public enum TicketPriority { diff --git a/src/Modmail.NET/Features/TicketType/Commands.cs b/src/Modmail.NET/Features/TicketType/Commands.cs deleted file mode 100644 index e8596db1..00000000 --- a/src/Modmail.NET/Features/TicketType/Commands.cs +++ /dev/null @@ -1,22 +0,0 @@ -using MediatR; -using Modmail.NET.Abstract; -using Modmail.NET.Attributes; - -namespace Modmail.NET.Features.TicketType; - -[PermissionCheck(nameof(AuthPolicy.ManageTicketTypes))] -public sealed record ProcessUpdateTicketTypeCommand(ulong AuthorizedUserId, Entities.TicketType TicketType) : IRequest; - -[PermissionCheck(nameof(AuthPolicy.ManageTicketTypes))] -public sealed record ProcessCreateTicketTypeCommand( - ulong AuthorizedUserId, - string Name, - string Emoji, - string Description, - long Order, - string EmbedMessageTitle, - string EmbedMessageContent) : IRequest; - -[PermissionCheck(nameof(AuthPolicy.ManageTicketTypes))] -public sealed record ProcessRemoveTicketTypeCommand(ulong AuthorizedUserId, Guid Id) : IRequest, - IPermissionCheck; \ No newline at end of file diff --git a/src/Modmail.NET/Features/TicketType/Queries.cs b/src/Modmail.NET/Features/TicketType/Queries.cs deleted file mode 100644 index 3fdb8425..00000000 --- a/src/Modmail.NET/Features/TicketType/Queries.cs +++ /dev/null @@ -1,19 +0,0 @@ -using MediatR; - -namespace Modmail.NET.Features.TicketType; - -public sealed record GetTicketTypeQuery( - Guid Id, - bool AllowNull = false) : IRequest; - -public sealed record GetTicketTypeBySearchQuery( - string NameOrKey, - bool AllowNull = false) : IRequest; - -public sealed record GetTicketTypeByChannelIdQuery( - ulong ChannelId, - bool AllowNull = false) : IRequest; - -public sealed record GetTicketTypeListQuery(bool OnlyActive = false) : IRequest>; - -public sealed record CheckTicketTypeExistsQuery(string NameOrKey) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/UserInfo/Commands.cs b/src/Modmail.NET/Features/User/Commands/UpdateDiscordUserCommand.cs similarity index 63% rename from src/Modmail.NET/Features/UserInfo/Commands.cs rename to src/Modmail.NET/Features/User/Commands/UpdateDiscordUserCommand.cs index afe79e9a..5fe3349a 100644 --- a/src/Modmail.NET/Features/UserInfo/Commands.cs +++ b/src/Modmail.NET/Features/User/Commands/UpdateDiscordUserCommand.cs @@ -1,7 +1,7 @@ using DSharpPlus.Entities; using MediatR; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; -namespace Modmail.NET.Features.UserInfo; +namespace Modmail.NET.Features.User.Commands; public sealed record UpdateDiscordUserCommand(DiscordUser DiscordUser) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/UserInfo/Handlers/GetDiscordUserInfoDictHandler.cs b/src/Modmail.NET/Features/User/Handlers/GetDiscordUserInfoDictHandler.cs similarity index 86% rename from src/Modmail.NET/Features/UserInfo/Handlers/GetDiscordUserInfoDictHandler.cs rename to src/Modmail.NET/Features/User/Handlers/GetDiscordUserInfoDictHandler.cs index 56d30417..e4ae5197 100644 --- a/src/Modmail.NET/Features/UserInfo/Handlers/GetDiscordUserInfoDictHandler.cs +++ b/src/Modmail.NET/Features/User/Handlers/GetDiscordUserInfoDictHandler.cs @@ -1,9 +1,10 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Modmail.NET.Database; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.User.Queries; -namespace Modmail.NET.Features.UserInfo.Handlers; +namespace Modmail.NET.Features.User.Handlers; public class GetDiscordUserInfoDictHandler : IRequestHandler> { diff --git a/src/Modmail.NET/Features/UserInfo/Handlers/GetDiscordUserInfoHandler.cs b/src/Modmail.NET/Features/User/Handlers/GetDiscordUserInfoHandler.cs similarity index 85% rename from src/Modmail.NET/Features/UserInfo/Handlers/GetDiscordUserInfoHandler.cs rename to src/Modmail.NET/Features/User/Handlers/GetDiscordUserInfoHandler.cs index 4d84b1f9..ec79298e 100644 --- a/src/Modmail.NET/Features/UserInfo/Handlers/GetDiscordUserInfoHandler.cs +++ b/src/Modmail.NET/Features/User/Handlers/GetDiscordUserInfoHandler.cs @@ -1,11 +1,14 @@ using MediatR; using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Exceptions; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.User.Commands; +using Modmail.NET.Features.User.Queries; +using Modmail.NET.Language; using NotFoundException = DSharpPlus.Exceptions.NotFoundException; -namespace Modmail.NET.Features.UserInfo.Handlers; +namespace Modmail.NET.Features.User.Handlers; public class GetDiscordUserInfoHandler : IRequestHandler { diff --git a/src/Modmail.NET/Features/UserInfo/Handlers/GetDiscordUserInfoListHandler.cs b/src/Modmail.NET/Features/User/Handlers/GetDiscordUserInfoListHandler.cs similarity index 81% rename from src/Modmail.NET/Features/UserInfo/Handlers/GetDiscordUserInfoListHandler.cs rename to src/Modmail.NET/Features/User/Handlers/GetDiscordUserInfoListHandler.cs index 01cfa16e..59dfe64b 100644 --- a/src/Modmail.NET/Features/UserInfo/Handlers/GetDiscordUserInfoListHandler.cs +++ b/src/Modmail.NET/Features/User/Handlers/GetDiscordUserInfoListHandler.cs @@ -1,9 +1,10 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Modmail.NET.Database; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.User.Queries; -namespace Modmail.NET.Features.UserInfo.Handlers; +namespace Modmail.NET.Features.User.Handlers; public class GetDiscordUserInfoListHandler : IRequestHandler> { diff --git a/src/Modmail.NET/Features/UserInfo/Handlers/UpdateDiscordUserHandler.cs b/src/Modmail.NET/Features/User/Handlers/UpdateDiscordUserHandler.cs similarity index 89% rename from src/Modmail.NET/Features/UserInfo/Handlers/UpdateDiscordUserHandler.cs rename to src/Modmail.NET/Features/User/Handlers/UpdateDiscordUserHandler.cs index 18babb87..cc9e3960 100644 --- a/src/Modmail.NET/Features/UserInfo/Handlers/UpdateDiscordUserHandler.cs +++ b/src/Modmail.NET/Features/User/Handlers/UpdateDiscordUserHandler.cs @@ -1,9 +1,10 @@ using MediatR; +using Modmail.NET.Common.Utils; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Utils; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.User.Commands; -namespace Modmail.NET.Features.UserInfo.Handlers; +namespace Modmail.NET.Features.User.Handlers; public class UpdateDiscordUserHandler : IRequestHandler { diff --git a/src/Modmail.NET/Jobs/DiscordUserInfoSyncJob.cs b/src/Modmail.NET/Features/User/Jobs/DiscordUserInfoSyncJob.cs similarity index 92% rename from src/Modmail.NET/Jobs/DiscordUserInfoSyncJob.cs rename to src/Modmail.NET/Features/User/Jobs/DiscordUserInfoSyncJob.cs index 36276219..bb3fead6 100644 --- a/src/Modmail.NET/Jobs/DiscordUserInfoSyncJob.cs +++ b/src/Modmail.NET/Features/User/Jobs/DiscordUserInfoSyncJob.cs @@ -3,12 +3,12 @@ using Microsoft.Extensions.DependencyInjection; using Modmail.NET.Abstract; using Modmail.NET.Database; -using Modmail.NET.Entities; -using Modmail.NET.Features.Bot; -using Modmail.NET.Features.UserInfo; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.DiscordBot.Queries; +using Modmail.NET.Features.User.Queries; using Serilog; -namespace Modmail.NET.Jobs; +namespace Modmail.NET.Features.User.Jobs; public class DiscordUserInfoSyncJob : HangfireRecurringJobBase { diff --git a/src/Modmail.NET/Features/User/Queries/GetDiscordUserInfoDictQuery.cs b/src/Modmail.NET/Features/User/Queries/GetDiscordUserInfoDictQuery.cs new file mode 100644 index 00000000..b8f18d72 --- /dev/null +++ b/src/Modmail.NET/Features/User/Queries/GetDiscordUserInfoDictQuery.cs @@ -0,0 +1,8 @@ +using MediatR; +using Modmail.NET.Attributes; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.User.Queries; + +[CachePolicy("GetDiscordUserInfoQuery", 5)] +public sealed record GetDiscordUserInfoDictQuery : IRequest>; \ No newline at end of file diff --git a/src/Modmail.NET/Features/User/Queries/GetDiscordUserInfoListQuery.cs b/src/Modmail.NET/Features/User/Queries/GetDiscordUserInfoListQuery.cs new file mode 100644 index 00000000..5c3d8b18 --- /dev/null +++ b/src/Modmail.NET/Features/User/Queries/GetDiscordUserInfoListQuery.cs @@ -0,0 +1,8 @@ +using MediatR; +using Modmail.NET.Attributes; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.User.Queries; + +[CachePolicy("GetDiscordUserInfoQuery", 5)] +public sealed record GetDiscordUserInfoListQuery : IRequest>; \ No newline at end of file diff --git a/src/Modmail.NET/Features/User/Queries/GetDiscordUserInfoQuery.cs b/src/Modmail.NET/Features/User/Queries/GetDiscordUserInfoQuery.cs new file mode 100644 index 00000000..ec6ed013 --- /dev/null +++ b/src/Modmail.NET/Features/User/Queries/GetDiscordUserInfoQuery.cs @@ -0,0 +1,8 @@ +using MediatR; +using Modmail.NET.Attributes; +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.User.Queries; + +[CachePolicy("GetDiscordUserInfoQuery", 60)] +public sealed record GetDiscordUserInfoQuery(ulong UserId) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/UserInfo/Queries.cs b/src/Modmail.NET/Features/UserInfo/Queries.cs deleted file mode 100644 index 84e0a986..00000000 --- a/src/Modmail.NET/Features/UserInfo/Queries.cs +++ /dev/null @@ -1,14 +0,0 @@ -using MediatR; -using Modmail.NET.Attributes; -using Modmail.NET.Entities; - -namespace Modmail.NET.Features.UserInfo; - -[CachePolicy("GetDiscordUserInfoQuery", 5)] -public sealed record GetDiscordUserInfoListQuery : IRequest>; - -[CachePolicy("GetDiscordUserInfoQuery", 5)] -public sealed record GetDiscordUserInfoDictQuery : IRequest>; - -[CachePolicy("GetDiscordUserInfoQuery", 60)] -public sealed record GetDiscordUserInfoQuery(ulong UserId) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/GlobalUsing.cs b/src/Modmail.NET/GlobalUsing.cs index a506b0fd..5f282702 100644 --- a/src/Modmail.NET/GlobalUsing.cs +++ b/src/Modmail.NET/GlobalUsing.cs @@ -1,2 +1 @@ -global using Modmail.NET.Language; -global using Modmail.NET.Static; \ No newline at end of file + \ No newline at end of file diff --git a/src/Modmail.NET/Language/LangProvider.cs b/src/Modmail.NET/Language/LangProvider.cs index 8ec39aed..23690add 100644 --- a/src/Modmail.NET/Language/LangProvider.cs +++ b/src/Modmail.NET/Language/LangProvider.cs @@ -1,6 +1,6 @@ using System.Text; using Microsoft.Extensions.Options; -using Modmail.NET.Utils; +using Modmail.NET.Common.Utils; using Newtonsoft.Json; using Serilog; diff --git a/src/Modmail.NET/Language/StringInteropProvider.cs b/src/Modmail.NET/Language/StringInteropProvider.cs index 3a507eee..23828673 100644 --- a/src/Modmail.NET/Language/StringInteropProvider.cs +++ b/src/Modmail.NET/Language/StringInteropProvider.cs @@ -1,6 +1,6 @@ using System.Text; using DSharpPlus.Entities; -using Modmail.NET.Entities; +using Modmail.NET.Database.Entities; namespace Modmail.NET.Language; diff --git a/src/Modmail.NET/Mappers/TicketDtoMapper.cs b/src/Modmail.NET/Mappers/TicketDtoMapper.cs deleted file mode 100644 index 6792beeb..00000000 --- a/src/Modmail.NET/Mappers/TicketDtoMapper.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Modmail.NET.Entities; -using Modmail.NET.Models.Dto; -using Riok.Mapperly.Abstractions; - -namespace Modmail.NET.Mappers; - -[Mapper] -public static partial class TicketDtoMapper -{ - public static partial IQueryable ProjectToDto(this IQueryable queryable); - - public static partial TicketDto ToDto(this Ticket entity); -} \ No newline at end of file diff --git a/src/Modmail.NET/Modmail.NET.csproj b/src/Modmail.NET/Modmail.NET.csproj index 7099dca8..9ace8c6e 100644 --- a/src/Modmail.NET/Modmail.NET.csproj +++ b/src/Modmail.NET/Modmail.NET.csproj @@ -75,4 +75,11 @@ + + + + + + + diff --git a/src/Modmail.NET/ModmailBot.cs b/src/Modmail.NET/ModmailBot.cs index e664ded7..23992666 100644 --- a/src/Modmail.NET/ModmailBot.cs +++ b/src/Modmail.NET/ModmailBot.cs @@ -2,9 +2,11 @@ using MediatR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Modmail.NET.Exceptions; -using Modmail.NET.Features.Guild; -using Modmail.NET.Features.UserInfo; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Static; +using Modmail.NET.Features.Guild.Commands; +using Modmail.NET.Features.Guild.Queries; +using Modmail.NET.Features.User.Commands; using Serilog; namespace Modmail.NET; diff --git a/src/Modmail.NET/Pipeline/CachingPipelineBehavior.cs b/src/Modmail.NET/Pipeline/CachingPipelineBehavior.cs index 4c3ceebf..e1e34f19 100644 --- a/src/Modmail.NET/Pipeline/CachingPipelineBehavior.cs +++ b/src/Modmail.NET/Pipeline/CachingPipelineBehavior.cs @@ -2,7 +2,7 @@ using MediatR; using Microsoft.Extensions.Caching.Memory; using Modmail.NET.Attributes; -using Modmail.NET.Utils; +using Modmail.NET.Common.Utils; using Serilog; namespace Modmail.NET.Pipeline; diff --git a/src/Modmail.NET/Pipeline/LoggerPipelineBehavior.cs b/src/Modmail.NET/Pipeline/LoggerPipelineBehavior.cs index fba66f36..f3e1ff7b 100644 --- a/src/Modmail.NET/Pipeline/LoggerPipelineBehavior.cs +++ b/src/Modmail.NET/Pipeline/LoggerPipelineBehavior.cs @@ -1,5 +1,5 @@ using MediatR; -using Modmail.NET.Abstract; +using Modmail.NET.Common.Exceptions; using Serilog; namespace Modmail.NET.Pipeline; @@ -13,7 +13,7 @@ public async Task Handle(TRequest request, RequestHandlerDelegate> AutoCompleteAsync(AutoCompleteContext context) { - const string cacheKey = "TeamProvider.Provider.AutoComplete"; - var cache = context.ServiceProvider.GetRequiredService(); - return await cache.GetOrCreateAsync(cacheKey, Get, new MemoryCacheEntryOptions { - AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(60) - }); - - async Task> Get(ICacheEntry cacheEntry) { - var scope = context.ServiceProvider.CreateScope(); - var sender = scope.ServiceProvider.GetRequiredService(); - var bot = scope.ServiceProvider.GetRequiredService(); - var teamsDbList = await sender.Send(new GetTeamListQuery(bot.Client.CurrentUser.Id)); - var teams = teamsDbList.Select(x => new DiscordAutoCompleteChoice(x.Name, x.Name)); - return await Task.FromResult(teams.AsEnumerable()); - } - } -} \ No newline at end of file diff --git a/src/Modmail.NET/Dependency.cs b/src/Modmail.NET/ServiceLocator.cs similarity index 98% rename from src/Modmail.NET/Dependency.cs rename to src/Modmail.NET/ServiceLocator.cs index 40a03271..d739f932 100644 --- a/src/Modmail.NET/Dependency.cs +++ b/src/Modmail.NET/ServiceLocator.cs @@ -1,6 +1,7 @@ using MediatR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Modmail.NET.Language; namespace Modmail.NET; diff --git a/src/Modmail.NET/Static/Const.cs b/src/Modmail.NET/Static/Const.cs deleted file mode 100644 index a5da5b7c..00000000 --- a/src/Modmail.NET/Static/Const.cs +++ /dev/null @@ -1,25 +0,0 @@ -using DSharpPlus.Entities; - -namespace Modmail.NET.Static; - -public static class Const -{ - public const string CategoryName = "Modmail"; - public const string LogChannelName = "📄modmail-logs"; - public const string TicketNameTemplate = "ticket-{0}"; - public const string HighPriorityEmoji = "🔴"; - public const string NormalPriorityEmoji = ""; - public const string LowPriorityEmoji = "🟢"; - public const int TicketTimeoutMinAllowedHours = 12; - public const int TicketTimeoutMaxAllowedHours = 24 * 7 * 4; - public const int TicketDataDeleteWaitDaysMin = 1; - public const int TicketDataDeleteWaitDaysMax = 365; - public const int StatisticsCalculateDaysMin = 30; - public const int StatisticsCalculateDaysMax = 365; - public const int DefaultStatisticsCalculateDays = 90; - public const string ThemeCookieName = "Modmail.NET.Theme"; - public const string AttachmentDownloadDirectory = "AttachmentDownloads"; - public const int HttpClientDownloadTimeoutSeconds = 90; - public const string ProcessedReactionDiscordEmojiUnicode = "✅"; - public static readonly DiscordActivity DiscordActivity = new(LangProvider.This.GetTranslation(LangKeys.ModerationConcerns), DiscordActivityType.ListeningTo); -} \ No newline at end of file diff --git a/src/Modmail.NET/Static/Interactions.cs b/src/Modmail.NET/Static/Interactions.cs deleted file mode 100644 index d067b481..00000000 --- a/src/Modmail.NET/Static/Interactions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using DSharpPlus.Entities; - -namespace Modmail.NET.Static; - -public static class Interactions -{ - public static DiscordInteractionResponseBuilder Error(string title, string message = "") { - return new DiscordInteractionResponseBuilder().AddEmbed(Embeds.Error(title, message)); - } - - public static DiscordInteractionResponseBuilder Success(string title, string message = "") { - return new DiscordInteractionResponseBuilder().AddEmbed(Embeds.Success(title, message)); - } - - public static DiscordInteractionResponseBuilder Info(string title, string message = "") { - return new DiscordInteractionResponseBuilder().AddEmbed(Embeds.Info(title, message)); - } - - public static DiscordInteractionResponseBuilder Warning(string title, string message = "") { - return new DiscordInteractionResponseBuilder().AddEmbed(Embeds.Warning(title, message)); - } -} \ No newline at end of file diff --git a/src/Modmail.NET/Static/TicketResponses.cs b/src/Modmail.NET/Static/TicketResponses.cs deleted file mode 100644 index 99684484..00000000 --- a/src/Modmail.NET/Static/TicketResponses.cs +++ /dev/null @@ -1,109 +0,0 @@ -using DSharpPlus.Entities; -using Modmail.NET.Entities; -using Modmail.NET.Extensions; -using Modmail.NET.Utils; - -namespace Modmail.NET.Static; - -/// -/// Contains the embed messages bot to send to ticket channels -/// -public static class TicketResponses -{ - public static DiscordMessageBuilder NewTicket(DiscordUser member, Guid ticketId) { - var embed = new DiscordEmbedBuilder() - .WithTitle(LangKeys.NewTicket.GetTranslation()) - .WithCustomTimestamp() - .WithDescription(LangKeys.NewTicketDescriptionMessage.GetTranslation()) - .WithAuthor(member.GetUsername(), iconUrl: member.AvatarUrl) - .AddField(LangKeys.User.GetTranslation(), member.Mention, true) - .AddField(LangKeys.TicketId.GetTranslation(), ticketId.ToString().ToUpper(), true) - .WithColor(Colors.TicketCreatedColor); - - var messageBuilder = new DiscordMessageBuilder() - .AddEmbed(embed) - .AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Danger, - UtilInteraction.BuildKey("close_ticket_with_reason", ticketId.ToString()), - LangKeys.CloseTicket.GetTranslation(), - emoji: new DiscordComponentEmoji("🔒")) - ); - return messageBuilder; - } - - public static DiscordEmbedBuilder NoteAdded(TicketNote note, DiscordUserInfo user) { - var embed = new DiscordEmbedBuilder() - .WithTitle(LangKeys.NoteAdded.GetTranslation()) - .WithDescription(note.Content) - .WithColor(Colors.NoteAddedColor) - .WithCustomTimestamp() - .WithUserAsAuthor(user); - return embed; - } - - public static DiscordEmbedBuilder AnonymousToggled(Ticket ticket) { - var embed2 = new DiscordEmbedBuilder() - .WithTitle(ticket.Anonymous - ? LangKeys.AnonymousModOn.GetTranslation() - : LangKeys.AnonymousModOff.GetTranslation()) - .WithColor(Colors.AnonymousToggledColor) - .WithCustomTimestamp() - .WithDescription(ticket.Anonymous - ? LangKeys.TicketSetAnonymousDescription.GetTranslation() - : LangKeys.TicketSetNotAnonymousDescription.GetTranslation()); - - if (ticket.OpenerUser is not null) embed2.WithUserAsAuthor(ticket.OpenerUser); - - - return embed2; - } - - public static DiscordEmbedBuilder TicketTypeChanged(DiscordUserInfo user, TicketType ticketType) { - var embed = new DiscordEmbedBuilder() - .WithTitle(LangKeys.TicketTypeChanged.GetTranslation()) - .WithUserAsAuthor(user) - .WithCustomTimestamp() - .WithColor(Colors.TicketTypeChangedColor); - if (ticketType is not null) - embed.WithDescription(string.Format(LangKeys.TicketTypeSet.GetTranslation(), ticketType.Emoji, ticketType.Name)); - else - embed.WithDescription(LangKeys.TicketTypeRemoved.GetTranslation()); - - return embed; - } - - public static DiscordEmbedBuilder TicketPriorityChanged(DiscordUserInfo modUser, Ticket ticket, TicketPriority oldPriority, TicketPriority newPriority) { - var embed = new DiscordEmbedBuilder() - .WithTitle(LangKeys.TicketPriorityChanged.GetTranslation()) - .WithColor(Colors.TicketPriorityChangedColor) - .WithCustomTimestamp() - .AddField(LangKeys.OldPriority.GetTranslation(), oldPriority.ToString(), true) - .AddField(LangKeys.NewPriority.GetTranslation(), newPriority.ToString(), true) - .WithUserAsAuthor(modUser); - return embed; - } - - public static DiscordMessageBuilder MessageReceived(DiscordMessage message, - TicketMessageAttachment[] attachments) { - var embed = new DiscordEmbedBuilder() - .WithDescription(message.Content) - .WithCustomTimestamp() - .WithColor(Colors.MessageReceivedColor) - .WithUserAsAuthor(message.Author); - - var msgBuilder = new DiscordMessageBuilder() - .AddEmbed(embed) - .AddAttachments(attachments); - return msgBuilder; - } - - public static DiscordEmbedBuilder MessageEdited(DiscordMessage message) { - var embed = new DiscordEmbedBuilder() - .WithDescription(message.Content) - .WithCustomTimestamp() - .WithColor(Colors.MessageReceivedColor) - .WithFooter(LangKeys.Edited.GetTranslation()) - .WithUserAsAuthor(message.Author); - - return embed; - } -} \ No newline at end of file diff --git a/src/Modmail.NET/Static/UserResponses.cs b/src/Modmail.NET/Static/UserResponses.cs deleted file mode 100644 index 55f5c07e..00000000 --- a/src/Modmail.NET/Static/UserResponses.cs +++ /dev/null @@ -1,158 +0,0 @@ -using DSharpPlus.Entities; -using Modmail.NET.Entities; -using Modmail.NET.Extensions; -using Modmail.NET.Utils; -using DiscordMessageBuilder = DSharpPlus.Entities.DiscordMessageBuilder; - -namespace Modmail.NET.Static; - -/// -/// Contains the messages bot to send to user -/// -public static class UserResponses -{ - public static DiscordEmbedBuilder FeedbackReceivedUpdateMessage(Ticket ticket) { - var feedbackDone = new DiscordEmbedBuilder() - .WithTitle(LangKeys.FeedbackReceived.GetTranslation()) - .WithCustomTimestamp() - .WithGuildInfoFooter() - .AddField(LangKeys.Star.GetTranslation(), LangKeys.StarEmoji.GetTranslation() + ticket.FeedbackStar) - .AddField(LangKeys.Feedback.GetTranslation(), ticket.FeedbackMessage) - .WithColor(Colors.FeedbackColor); - return feedbackDone; - } - - - public static DiscordMessageBuilder YourTicketHasBeenClosed(Ticket ticket, GuildOption guildOption, Uri transcriptUri) { - var messageBuilder = new DiscordMessageBuilder(); - var embedBuilder = new DiscordEmbedBuilder() - .WithTitle(LangKeys.YourTicketHasBeenClosed.GetTranslation()) - .WithDescription(LangKeys.YourTicketHasBeenClosedDescription.GetTranslation()) - .WithGuildInfoFooter(guildOption) - .WithCustomTimestamp() - .WithColor(Colors.TicketClosedColor); - - var closingMessage = LangKeys.ClosingMessageDescription.GetTranslation(); - - if (!string.IsNullOrEmpty(closingMessage)) embedBuilder.WithDescription(closingMessage); - - if (!string.IsNullOrEmpty(ticket.CloseReason)) embedBuilder.AddField(LangKeys.CloseReason.GetTranslation(), ticket.CloseReason); - - if (transcriptUri is not null) messageBuilder.AddComponents(new DiscordLinkButtonComponent(transcriptUri.AbsoluteUri, LangKeys.Transcript.GetTranslation())); - - messageBuilder.AddEmbed(embedBuilder); - return messageBuilder; - } - - public static DiscordMessageBuilder GiveFeedbackMessage(Ticket ticket, GuildOption guildOption) { - var ticketFeedbackMsgToUser = new DiscordMessageBuilder(); - var starList = new List { - new DiscordButtonComponent(DiscordButtonStyle.Primary, UtilInteraction.BuildKey("star", 1, ticket.Id), "1", false, new DiscordComponentEmoji("⭐")), - new DiscordButtonComponent(DiscordButtonStyle.Primary, UtilInteraction.BuildKey("star", 2, ticket.Id), "2", false, new DiscordComponentEmoji("⭐")), - new DiscordButtonComponent(DiscordButtonStyle.Primary, UtilInteraction.BuildKey("star", 3, ticket.Id), "3", false, new DiscordComponentEmoji("⭐")), - new DiscordButtonComponent(DiscordButtonStyle.Primary, UtilInteraction.BuildKey("star", 4, ticket.Id), "4", false, new DiscordComponentEmoji("⭐")), - new DiscordButtonComponent(DiscordButtonStyle.Primary, UtilInteraction.BuildKey("star", 5, ticket.Id), "5", false, new DiscordComponentEmoji("⭐")) - }; - - var ticketFeedbackEmbed = new DiscordEmbedBuilder() - .WithTitle(LangKeys.Feedback.GetTranslation()) - .WithDescription(LangKeys.FeedbackDescription.GetTranslation()) - .WithCustomTimestamp() - .WithGuildInfoFooter(guildOption) - .WithColor(Colors.FeedbackColor); - - var response = ticketFeedbackMsgToUser - .AddEmbed(ticketFeedbackEmbed) - .AddComponents(starList); - return response; - } - - public static DiscordEmbedBuilder TicketPriorityChanged(GuildOption guildOption, DiscordUserInfo info, Ticket ticket, TicketPriority oldPriority, TicketPriority newPriority) { - var embed = new DiscordEmbedBuilder() - .WithGuildInfoFooter(guildOption) - .WithTitle(LangKeys.TicketPriorityChanged.GetTranslation()) - .WithCustomTimestamp() - .WithColor(Colors.TicketPriorityChangedColor) - .AddField(LangKeys.OldPriority.GetTranslation(), oldPriority.ToString(), true) - .AddField(LangKeys.NewPriority.GetTranslation(), newPriority.ToString(), true); - if (!ticket.Anonymous) embed.WithUserAsAuthor(info); - // else embed.WithUserAsAuthor(ModmailBot.This.Client.CurrentUser); - return embed; - } - - - public static DiscordEmbedBuilder YouHaveBeenBlacklisted(string reason = null) { - var embed = new DiscordEmbedBuilder() - .WithTitle(LangKeys.YouHaveBeenBlacklisted.GetTranslation()) - .WithDescription(LangKeys.YouHaveBeenBlacklistedDescription.GetTranslation()) - .WithGuildInfoFooter() - .WithCustomTimestamp() - .WithColor(Colors.ErrorColor); - - if (!string.IsNullOrEmpty(reason)) embed.AddField(LangKeys.Reason.GetTranslation(), reason); - - return embed; - } - - public static DiscordMessageBuilder YouHaveCreatedNewTicket(DiscordGuild guild, - GuildOption option, - List ticketTypes, - Guid ticketId) { - var embed = new DiscordEmbedBuilder() - .WithTitle(LangKeys.YouHaveCreatedNewTicket.GetTranslation()) - .WithFooter(guild.Name, guild.IconUrl) - .WithCustomTimestamp() - .WithColor(Colors.TicketCreatedColor); - var greetingMessage = LangKeys.GreetingMessageDescription.GetTranslation(); - if (!string.IsNullOrEmpty(greetingMessage)) - embed.WithDescription(greetingMessage); - - var builder = new DiscordMessageBuilder() - .AddEmbed(embed); - - if (ticketTypes.Count > 0) { - var selectBox = new DiscordSelectComponent(UtilInteraction.BuildKey("ticket_type", ticketId.ToString()), - LangKeys.PleaseSelectATicketType.GetTranslation(), - ticketTypes.Select(x => new DiscordSelectComponentOption(x.Name, - x.Key.ToString(), - x.Description, - false, - !string.IsNullOrWhiteSpace(x.Emoji) - ? new DiscordComponentEmoji(x.Emoji) - : null)) - .ToList()); - builder.AddComponents(selectBox); - } - - return builder; - } - - - public static DiscordEmbedBuilder YouHaveBeenRemovedFromBlacklist(DiscordUserInfo user) { - var embed = new DiscordEmbedBuilder() - .WithTitle(LangKeys.YouHaveBeenRemovedFromBlacklist.GetTranslation()) - .WithDescription(LangKeys.YouHaveBeenRemovedFromBlacklistDescription.GetTranslation()) - .WithGuildInfoFooter() - .WithCustomTimestamp() - .WithUserAsAuthor(user) - .WithColor(Colors.SuccessColor); - return embed; - } - - public static DiscordMessageBuilder MessageReceived(DiscordMessage message, TicketMessageAttachment[] attachments, bool anonymous) { - var embed = new DiscordEmbedBuilder() - .WithDescription(message.Content) - .WithGuildInfoFooter() - .WithCustomTimestamp() - .WithColor(Colors.MessageReceivedColor); - - if (!anonymous) embed.WithUserAsAuthor(message.Author); - - var msg = new DiscordMessageBuilder(); - - msg.AddEmbed(embed); - msg.AddAttachments(attachments); - - return msg; - } -} \ No newline at end of file diff --git a/src/Modmail.NET/Static/Webhooks.cs b/src/Modmail.NET/Static/Webhooks.cs deleted file mode 100644 index 96572bcf..00000000 --- a/src/Modmail.NET/Static/Webhooks.cs +++ /dev/null @@ -1,22 +0,0 @@ -using DSharpPlus.Entities; - -namespace Modmail.NET.Static; - -public static class Webhooks -{ - public static DiscordWebhookBuilder Error(string title, string message = "") { - return new DiscordWebhookBuilder().AddEmbed(Embeds.Error(title, message)); - } - - public static DiscordWebhookBuilder Success(string title, string message = "") { - return new DiscordWebhookBuilder().AddEmbed(Embeds.Success(title, message)); - } - - public static DiscordWebhookBuilder Info(string title, string message = "") { - return new DiscordWebhookBuilder().AddEmbed(Embeds.Info(title, message)); - } - - public static DiscordWebhookBuilder Warning(string title, string message = "") { - return new DiscordWebhookBuilder().AddEmbed(Embeds.Warning(title, message)); - } -} \ No newline at end of file From 8b4649bfbde187da2bafaebb92c1707527978a96 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:13:31 +0300 Subject: [PATCH 17/47] fix: Properly handle anonymous flag during mod message updates - Resolved an issue where message updates by moderators were not correctly propagating the anonymous status of the ticket. - Created a `MessageEdited` handler to be fired when bot is the user that is editing. - Removed the "Edited" text from updated messages - Updated code to check both the ticket's and the guild's anonymous settings. --- .../Events/OnMessageUpdatedEvent.cs | 39 ++++++++++--------- .../Ticket/Helpers/TicketBotMessages.cs | 14 ++++++- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/Modmail.NET/Features/DiscordBot/Events/OnMessageUpdatedEvent.cs b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageUpdatedEvent.cs index 5fb87fd2..682b3ca0 100644 --- a/src/Modmail.NET/Features/DiscordBot/Events/OnMessageUpdatedEvent.cs +++ b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageUpdatedEvent.cs @@ -8,6 +8,7 @@ using Modmail.NET.Common.Aspects; using Modmail.NET.Common.Utils; using Modmail.NET.Database; +using Modmail.NET.Features.Guild.Queries; using Modmail.NET.Features.Ticket.Helpers; using Modmail.NET.Features.User.Commands; using Serilog; @@ -111,10 +112,12 @@ MessageUpdatedEventArgs args await UpdateMirroredMessage( client, + scope, ticket.ModMessageChannelId, messageEntity.BotMessageId, args.MessageBefore, - args.Message + args.Message, + ticket.Anonymous ); } @@ -174,34 +177,34 @@ MessageUpdatedEventArgs args await UpdateMirroredMessage( client, + scope, ticket.PrivateMessageChannelId, messageEntity.BotMessageId, args.MessageBefore, - args.Message + args.Message, + ticket.Anonymous ); } private static async Task UpdateMirroredMessage( DiscordClient client, - ulong? channelId, - ulong? messageId, + IServiceScope scope, + ulong channelId, + ulong messageId, DiscordMessage oldMessage, - DiscordMessage updatedMessage + DiscordMessage updatedMessage, + bool anonymous ) { - if (!channelId.HasValue || !messageId.HasValue) { - Log.Warning( - "[{Source}] Cannot update mirrored message. Invalid ChannelId or MessageId. ChannelId: {ChannelId}, MessageId: {MessageId}", - nameof(OnMessageUpdatedEvent), - channelId, - messageId - ); - return; - } - try { - var channel = await client.GetChannelAsync(channelId.Value); - var message = await channel.GetMessageAsync(messageId.Value); - var embed = TicketBotMessages.Ticket.MessageEdited(updatedMessage); + var sender = scope.ServiceProvider.GetRequiredService(); + var option = await sender.Send(new GetGuildOptionQuery(false)); + + var channel = await client.GetChannelAsync(channelId); + var message = await channel.GetMessageAsync(messageId); + var embed = + updatedMessage.Channel!.IsPrivate + ? TicketBotMessages.Ticket.MessageEdited(updatedMessage) + : TicketBotMessages.User.MessageEdited(updatedMessage, option.AlwaysAnonymous || anonymous); //TODO: Add support for removing attachment files from message on message update event //Currently the library does not support removing single attachments, either we have to remove all of them diff --git a/src/Modmail.NET/Features/Ticket/Helpers/TicketBotMessages.cs b/src/Modmail.NET/Features/Ticket/Helpers/TicketBotMessages.cs index 41fcedc1..e5537b27 100644 --- a/src/Modmail.NET/Features/Ticket/Helpers/TicketBotMessages.cs +++ b/src/Modmail.NET/Features/Ticket/Helpers/TicketBotMessages.cs @@ -132,6 +132,18 @@ public static DiscordMessageBuilder MessageReceived(DiscordMessage message, Tick return msg; } + + public static DiscordEmbedBuilder MessageEdited(DiscordMessage message, bool anonymous) { + var embed = new DiscordEmbedBuilder() + .WithDescription(message.Content) + .WithGuildInfoFooter() + .WithCustomTimestamp() + .WithColor(ModmailColors.MessageReceivedColor); + + if (!anonymous) embed.WithUserAsAuthor(message.Author); + + return embed; + } } public static class Ticket @@ -227,9 +239,7 @@ public static DiscordEmbedBuilder MessageEdited(DiscordMessage message) { .WithDescription(message.Content) .WithCustomTimestamp() .WithColor(ModmailColors.MessageReceivedColor) - .WithFooter(LangKeys.Edited.GetTranslation()) .WithUserAsAuthor(message.Author); - return embed; } } From e18c1a19071682610950e9db4f76819f58b73552 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:06:57 +0300 Subject: [PATCH 18/47] feat(TicketMessageHistory.cs): new table added and migrated - Ticket message history table is implemented, this table will be used for tracking message update changes to message contents --- .../Database/Entities/TicketMessageHistory.cs | 23 + ...TicketMessageHistoryTableAdded.Designer.cs | 679 ++++++++++++++++++ ...07174400_TicketMessageHistoryTableAdded.cs | 48 ++ .../ModmailDbContextModelSnapshot.cs | 105 ++- src/Modmail.NET/Database/ModmailDbContext.cs | 1 + 5 files changed, 825 insertions(+), 31 deletions(-) create mode 100644 src/Modmail.NET/Database/Entities/TicketMessageHistory.cs create mode 100644 src/Modmail.NET/Database/Migrations/20250407174400_TicketMessageHistoryTableAdded.Designer.cs create mode 100644 src/Modmail.NET/Database/Migrations/20250407174400_TicketMessageHistoryTableAdded.cs diff --git a/src/Modmail.NET/Database/Entities/TicketMessageHistory.cs b/src/Modmail.NET/Database/Entities/TicketMessageHistory.cs new file mode 100644 index 00000000..c5741198 --- /dev/null +++ b/src/Modmail.NET/Database/Entities/TicketMessageHistory.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Abstract; + +namespace Modmail.NET.Database.Entities; + +public class TicketMessageHistory : IHasRegisterDate, + IEntity +{ + public Guid Id { get; set; } + + [MaxLength(DbLength.Message)] + public required string MessageContentBefore { get; set; } = null; + + [MaxLength(DbLength.Message)] + public required string MessageContentAfter { get; set; } = null; + + public required Guid TicketMessageId { get; set; } + + //FK + public virtual TicketMessage TicketMessage { get; set; } + public DateTime RegisterDateUtc { get; set; } +} \ No newline at end of file diff --git a/src/Modmail.NET/Database/Migrations/20250407174400_TicketMessageHistoryTableAdded.Designer.cs b/src/Modmail.NET/Database/Migrations/20250407174400_TicketMessageHistoryTableAdded.Designer.cs new file mode 100644 index 00000000..a06d0ac7 --- /dev/null +++ b/src/Modmail.NET/Database/Migrations/20250407174400_TicketMessageHistoryTableAdded.Designer.cs @@ -0,0 +1,679 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Modmail.NET.Database; + +#nullable disable + +namespace Modmail.NET.Database.Migrations +{ + [DbContext(typeof(ModmailDbContext))] + [Migration("20250407174400_TicketMessageHistoryTableAdded")] + partial class TicketMessageHistoryTableAdded + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => + { + b.Property("Id") + .HasColumnType("decimal(20,0)"); + + b.Property("AvatarUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("BannerUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Email") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Locale") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.ToTable("DiscordUserInfos", t => + { + t.HasCheckConstraint("CK_DiscordUserInfos_Username_MinLength", "LEN([Username]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildOption", b => + { + b.Property("GuildId") + .HasColumnType("decimal(20,0)"); + + b.Property("AlwaysAnonymous") + .HasColumnType("bit"); + + b.Property("BannerUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("CategoryId") + .HasColumnType("decimal(20,0)"); + + b.Property("IconUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("LogChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("ManageBlacklistMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageHangfireMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTeamsMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTicketMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTicketTypeMinAccessLevel") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PublicTranscripts") + .HasColumnType("bit"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SendTranscriptLinkToUser") + .HasColumnType("bit"); + + b.Property("StatisticsCalculateDays") + .HasColumnType("int"); + + b.Property("TakeFeedbackAfterClosing") + .HasColumnType("bit"); + + b.Property("TicketDataDeleteWaitDays") + .HasColumnType("int"); + + b.Property("TicketTimeoutHours") + .HasColumnType("bigint"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("GuildId"); + + b.ToTable("GuildOptions", t => + { + t.HasCheckConstraint("CK_GuildOptions_Name_MinLength", "LEN([Name]) >= 1"); + + t.HasCheckConstraint("CK_GuildOptions_StatisticsCalculateDays_Range", "[StatisticsCalculateDays] BETWEEN -1 AND 365"); + + t.HasCheckConstraint("CK_GuildOptions_TicketDataDeleteWaitDays_Range", "[TicketDataDeleteWaitDays] BETWEEN -1 AND 365"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowAccessToWebPanel") + .HasColumnType("bit"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PermissionLevel") + .HasColumnType("int"); + + b.Property("PingOnNewMessage") + .HasColumnType("bit"); + + b.Property("PingOnNewTicket") + .HasColumnType("bit"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("GuildTeams", t => + { + t.HasCheckConstraint("CK_GuildTeams_Name_MinLength", "LEN([Name]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("GuildTeamId") + .HasColumnType("uniqueidentifier"); + + b.Property("Key") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GuildTeamId"); + + b.ToTable("GuildTeamMembers"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Statistic", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AvgResponseTimeSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketClosedSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketsClosedPerDay") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketsOpenedPerDay") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("FastestClosedTicketSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SlowestClosedTicketSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Anonymous") + .HasColumnType("bit"); + + b.Property("AssignedUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("BotTicketCreatedMessageInDmId") + .HasColumnType("decimal(20,0)"); + + b.Property("CloseReason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("ClosedDateUtc") + .HasColumnType("datetime2"); + + b.Property("CloserUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("FeedbackMessage") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("FeedbackStar") + .HasColumnType("int"); + + b.Property("InitialMessageId") + .HasColumnType("decimal(20,0)"); + + b.Property("IsForcedClosed") + .HasColumnType("bit"); + + b.Property("LastMessageDateUtc") + .HasColumnType("datetime2"); + + b.Property("ModMessageChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("OpenerUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("PrivateMessageChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketTypeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AssignedUserId"); + + b.HasIndex("CloserUserId"); + + b.HasIndex("OpenerUserId"); + + b.HasIndex("TicketTypeId"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DiscordUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("Reason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DiscordUserId") + .IsUnique(); + + b.ToTable("TicketBlacklists"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BotMessageId") + .HasColumnType("decimal(20,0)"); + + b.Property("MessageContent") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageDiscordId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SenderUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("SentByMod") + .HasColumnType("bit"); + + b.Property("TicketId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("SenderUserId"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketMessages"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("FileSize") + .HasColumnType("int"); + + b.Property("Height") + .HasColumnType("int"); + + b.Property("MediaType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ProxyUrl") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Width") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageAttachments", t => + { + t.HasCheckConstraint("CK_TicketMessageAttachments_FileName_MinLength", "LEN([FileName]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_MediaType_MinLength", "LEN([MediaType]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_ProxyUrl_MinLength", "LEN([ProxyUrl]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_Url_MinLength", "LEN([Url]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("MessageContentAfter") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageContentBefore") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageHistory"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DiscordUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketNotes", t => + { + t.HasCheckConstraint("CK_TicketNotes_Content_MinLength", "LEN([Content]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmbedMessageContent") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmbedMessageTitle") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Emoji") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("TicketTypes", t => + { + t.HasCheckConstraint("CK_TicketTypes_Description_MinLength", "LEN([Description]) >= 1"); + + t.HasCheckConstraint("CK_TicketTypes_Key_MinLength", "LEN([Key]) >= 1"); + + t.HasCheckConstraint("CK_TicketTypes_Name_MinLength", "LEN([Name]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => + { + b.HasOne("Modmail.NET.Database.Entities.GuildTeam", "GuildTeam") + .WithMany("GuildTeamMembers") + .HasForeignKey("GuildTeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuildTeam"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "AssignedUser") + .WithMany("AssignedTickets") + .HasForeignKey("AssignedUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "CloserUser") + .WithMany("ClosedTickets") + .HasForeignKey("CloserUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "OpenerUser") + .WithMany("OpenedTickets") + .HasForeignKey("OpenerUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Modmail.NET.Database.Entities.TicketType", "TicketType") + .WithMany() + .HasForeignKey("TicketTypeId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("AssignedUser"); + + b.Navigation("CloserUser"); + + b.Navigation("OpenerUser"); + + b.Navigation("TicketType"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "DiscordUser") + .WithMany() + .HasForeignKey("DiscordUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscordUser"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", null) + .WithMany() + .HasForeignKey("SenderUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => + { + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", null) + .WithMany("Attachments") + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", "TicketMessage") + .WithMany() + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TicketMessage"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) + .WithMany("TicketNotes") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => + { + b.Navigation("AssignedTickets"); + + b.Navigation("ClosedTickets"); + + b.Navigation("OpenedTickets"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => + { + b.Navigation("GuildTeamMembers"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.Navigation("Messages"); + + b.Navigation("TicketNotes"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.Navigation("Attachments"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Modmail.NET/Database/Migrations/20250407174400_TicketMessageHistoryTableAdded.cs b/src/Modmail.NET/Database/Migrations/20250407174400_TicketMessageHistoryTableAdded.cs new file mode 100644 index 00000000..ebe27a0b --- /dev/null +++ b/src/Modmail.NET/Database/Migrations/20250407174400_TicketMessageHistoryTableAdded.cs @@ -0,0 +1,48 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Modmail.NET.Database.Migrations +{ + /// + public partial class TicketMessageHistoryTableAdded : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "TicketMessageHistory", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + MessageContentBefore = table.Column(type: "nvarchar(max)", maxLength: 2147483647, nullable: true), + MessageContentAfter = table.Column(type: "nvarchar(max)", maxLength: 2147483647, nullable: true), + TicketMessageId = table.Column(type: "uniqueidentifier", nullable: false), + RegisterDateUtc = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TicketMessageHistory", x => x.Id); + table.ForeignKey( + name: "FK_TicketMessageHistory_TicketMessages_TicketMessageId", + column: x => x.TicketMessageId, + principalTable: "TicketMessages", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_TicketMessageHistory_TicketMessageId", + table: "TicketMessageHistory", + column: "TicketMessageId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TicketMessageHistory"); + } + } +} diff --git a/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs b/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs index f2e98e8d..6febac33 100644 --- a/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs +++ b/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs @@ -22,7 +22,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - modelBuilder.Entity("Modmail.NET.Entities.DiscordUserInfo", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => { b.Property("Id") .HasColumnType("decimal(20,0)"); @@ -62,7 +62,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); - modelBuilder.Entity("Modmail.NET.Entities.GuildOption", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildOption", b => { b.Property("GuildId") .HasColumnType("decimal(20,0)"); @@ -143,7 +143,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); - modelBuilder.Entity("Modmail.NET.Entities.GuildTeam", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -183,7 +183,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); - modelBuilder.Entity("Modmail.NET.Entities.GuildTeamMember", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -208,7 +208,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("GuildTeamMembers"); }); - modelBuilder.Entity("Modmail.NET.Entities.Statistic", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.Statistic", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -246,7 +246,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Statistics"); }); - modelBuilder.Entity("Modmail.NET.Entities.Ticket", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -318,7 +318,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Tickets"); }); - modelBuilder.Entity("Modmail.NET.Entities.TicketBlacklist", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -342,7 +342,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("TicketBlacklists"); }); - modelBuilder.Entity("Modmail.NET.Entities.TicketMessage", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -351,6 +351,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("BotMessageId") .HasColumnType("decimal(20,0)"); + b.Property("ChangeStatus") + .HasColumnType("int"); + b.Property("MessageContent") .HasMaxLength(2147483647) .HasColumnType("nvarchar(max)"); @@ -379,7 +382,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("TicketMessages"); }); - modelBuilder.Entity("Modmail.NET.Entities.TicketMessageAttachment", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -433,7 +436,34 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); - modelBuilder.Entity("Modmail.NET.Entities.TicketNote", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("MessageContentAfter") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageContentBefore") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageHistory"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -463,7 +493,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); - modelBuilder.Entity("Modmail.NET.Entities.TicketType", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketType", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -520,9 +550,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); - modelBuilder.Entity("Modmail.NET.Entities.GuildTeamMember", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => { - b.HasOne("Modmail.NET.Entities.GuildTeam", "GuildTeam") + b.HasOne("Modmail.NET.Database.Entities.GuildTeam", "GuildTeam") .WithMany("GuildTeamMembers") .HasForeignKey("GuildTeamId") .OnDelete(DeleteBehavior.Cascade) @@ -531,25 +561,25 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("GuildTeam"); }); - modelBuilder.Entity("Modmail.NET.Entities.Ticket", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => { - b.HasOne("Modmail.NET.Entities.DiscordUserInfo", "AssignedUser") + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "AssignedUser") .WithMany("AssignedTickets") .HasForeignKey("AssignedUserId") .OnDelete(DeleteBehavior.Restrict); - b.HasOne("Modmail.NET.Entities.DiscordUserInfo", "CloserUser") + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "CloserUser") .WithMany("ClosedTickets") .HasForeignKey("CloserUserId") .OnDelete(DeleteBehavior.Restrict); - b.HasOne("Modmail.NET.Entities.DiscordUserInfo", "OpenerUser") + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "OpenerUser") .WithMany("OpenedTickets") .HasForeignKey("OpenerUserId") .OnDelete(DeleteBehavior.Restrict) .IsRequired(); - b.HasOne("Modmail.NET.Entities.TicketType", "TicketType") + b.HasOne("Modmail.NET.Database.Entities.TicketType", "TicketType") .WithMany() .HasForeignKey("TicketTypeId") .OnDelete(DeleteBehavior.Restrict); @@ -563,9 +593,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("TicketType"); }); - modelBuilder.Entity("Modmail.NET.Entities.TicketBlacklist", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => { - b.HasOne("Modmail.NET.Entities.DiscordUserInfo", "DiscordUser") + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "DiscordUser") .WithMany() .HasForeignKey("DiscordUserId") .OnDelete(DeleteBehavior.Restrict) @@ -574,40 +604,51 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("DiscordUser"); }); - modelBuilder.Entity("Modmail.NET.Entities.TicketMessage", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => { - b.HasOne("Modmail.NET.Entities.DiscordUserInfo", null) + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", null) .WithMany() .HasForeignKey("SenderUserId") .OnDelete(DeleteBehavior.Restrict) .IsRequired(); - b.HasOne("Modmail.NET.Entities.Ticket", null) + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) .WithMany("Messages") .HasForeignKey("TicketId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); - modelBuilder.Entity("Modmail.NET.Entities.TicketMessageAttachment", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => { - b.HasOne("Modmail.NET.Entities.TicketMessage", null) + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", null) .WithMany("Attachments") .HasForeignKey("TicketMessageId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); - modelBuilder.Entity("Modmail.NET.Entities.TicketNote", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => { - b.HasOne("Modmail.NET.Entities.Ticket", null) + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", "TicketMessage") + .WithMany("History") + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TicketMessage"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) .WithMany("TicketNotes") .HasForeignKey("TicketId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); - modelBuilder.Entity("Modmail.NET.Entities.DiscordUserInfo", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => { b.Navigation("AssignedTickets"); @@ -616,21 +657,23 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("OpenedTickets"); }); - modelBuilder.Entity("Modmail.NET.Entities.GuildTeam", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => { b.Navigation("GuildTeamMembers"); }); - modelBuilder.Entity("Modmail.NET.Entities.Ticket", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => { b.Navigation("Messages"); b.Navigation("TicketNotes"); }); - modelBuilder.Entity("Modmail.NET.Entities.TicketMessage", b => + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => { b.Navigation("Attachments"); + + b.Navigation("History"); }); #pragma warning restore 612, 618 } diff --git a/src/Modmail.NET/Database/ModmailDbContext.cs b/src/Modmail.NET/Database/ModmailDbContext.cs index 08c35e20..cd664a75 100644 --- a/src/Modmail.NET/Database/ModmailDbContext.cs +++ b/src/Modmail.NET/Database/ModmailDbContext.cs @@ -11,6 +11,7 @@ public ModmailDbContext(DbContextOptions options) : base(optio public DbSet Tickets { get; set; } = null!; public DbSet TicketMessageAttachments { get; set; } = null!; public DbSet TicketMessages { get; set; } = null!; + public DbSet TicketMessageHistory { get; set; } = null!; public DbSet GuildOptions { get; set; } = null!; public DbSet GuildTeams { get; set; } = null!; public DbSet GuildTeamMembers { get; set; } = null!; From f85bb06fc4a2d7972842483e35b32163952c04a7 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:08:45 +0300 Subject: [PATCH 19/47] feat: Implement tracking of TicketMessage status changes - Added a new `TicketMessageChangeStatus` enum column to the `TicketMessage` entity. - This column indicates the current state of a ticket message (e.g., None, Deleted, Updated). - This enhancement enables the web UI to present a history of message modifications. --- .../Database/Entities/TicketMessage.cs | 6 +- ...ketMessageAddedColChangeStatus.Designer.cs | 684 ++++++++++++++++++ ...74928_TicketMessageAddedColChangeStatus.cs | 29 + .../Static/TicketMessageChangeStatus.cs | 8 + 4 files changed, 726 insertions(+), 1 deletion(-) create mode 100644 src/Modmail.NET/Database/Migrations/20250407174928_TicketMessageAddedColChangeStatus.Designer.cs create mode 100644 src/Modmail.NET/Database/Migrations/20250407174928_TicketMessageAddedColChangeStatus.cs create mode 100644 src/Modmail.NET/Features/Ticket/Static/TicketMessageChangeStatus.cs diff --git a/src/Modmail.NET/Database/Entities/TicketMessage.cs b/src/Modmail.NET/Database/Entities/TicketMessage.cs index cbf6e2cf..c3fc1d77 100644 --- a/src/Modmail.NET/Database/Entities/TicketMessage.cs +++ b/src/Modmail.NET/Database/Entities/TicketMessage.cs @@ -4,6 +4,7 @@ using Modmail.NET.Common.Static; using Modmail.NET.Common.Utils; using Modmail.NET.Database.Abstract; +using Modmail.NET.Features.Ticket.Static; namespace Modmail.NET.Database.Entities; @@ -21,8 +22,11 @@ public class TicketMessage : IHasRegisterDate, public required bool SentByMod { get; set; } + public TicketMessageChangeStatus ChangeStatus { get; set; } = TicketMessageChangeStatus.None; + //FK - public List Attachments { get; set; } + public virtual List Attachments { get; set; } + public virtual List History { get; set; } public ulong BotMessageId { get; set; } public DateTime RegisterDateUtc { get; set; } diff --git a/src/Modmail.NET/Database/Migrations/20250407174928_TicketMessageAddedColChangeStatus.Designer.cs b/src/Modmail.NET/Database/Migrations/20250407174928_TicketMessageAddedColChangeStatus.Designer.cs new file mode 100644 index 00000000..23d3181f --- /dev/null +++ b/src/Modmail.NET/Database/Migrations/20250407174928_TicketMessageAddedColChangeStatus.Designer.cs @@ -0,0 +1,684 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Modmail.NET.Database; + +#nullable disable + +namespace Modmail.NET.Database.Migrations +{ + [DbContext(typeof(ModmailDbContext))] + [Migration("20250407174928_TicketMessageAddedColChangeStatus")] + partial class TicketMessageAddedColChangeStatus + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => + { + b.Property("Id") + .HasColumnType("decimal(20,0)"); + + b.Property("AvatarUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("BannerUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Email") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Locale") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.ToTable("DiscordUserInfos", t => + { + t.HasCheckConstraint("CK_DiscordUserInfos_Username_MinLength", "LEN([Username]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildOption", b => + { + b.Property("GuildId") + .HasColumnType("decimal(20,0)"); + + b.Property("AlwaysAnonymous") + .HasColumnType("bit"); + + b.Property("BannerUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("CategoryId") + .HasColumnType("decimal(20,0)"); + + b.Property("IconUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("LogChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("ManageBlacklistMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageHangfireMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTeamsMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTicketMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTicketTypeMinAccessLevel") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PublicTranscripts") + .HasColumnType("bit"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SendTranscriptLinkToUser") + .HasColumnType("bit"); + + b.Property("StatisticsCalculateDays") + .HasColumnType("int"); + + b.Property("TakeFeedbackAfterClosing") + .HasColumnType("bit"); + + b.Property("TicketDataDeleteWaitDays") + .HasColumnType("int"); + + b.Property("TicketTimeoutHours") + .HasColumnType("bigint"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("GuildId"); + + b.ToTable("GuildOptions", t => + { + t.HasCheckConstraint("CK_GuildOptions_Name_MinLength", "LEN([Name]) >= 1"); + + t.HasCheckConstraint("CK_GuildOptions_StatisticsCalculateDays_Range", "[StatisticsCalculateDays] BETWEEN -1 AND 365"); + + t.HasCheckConstraint("CK_GuildOptions_TicketDataDeleteWaitDays_Range", "[TicketDataDeleteWaitDays] BETWEEN -1 AND 365"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowAccessToWebPanel") + .HasColumnType("bit"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PermissionLevel") + .HasColumnType("int"); + + b.Property("PingOnNewMessage") + .HasColumnType("bit"); + + b.Property("PingOnNewTicket") + .HasColumnType("bit"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("GuildTeams", t => + { + t.HasCheckConstraint("CK_GuildTeams_Name_MinLength", "LEN([Name]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("GuildTeamId") + .HasColumnType("uniqueidentifier"); + + b.Property("Key") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GuildTeamId"); + + b.ToTable("GuildTeamMembers"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Statistic", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AvgResponseTimeSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketClosedSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketsClosedPerDay") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketsOpenedPerDay") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("FastestClosedTicketSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SlowestClosedTicketSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Anonymous") + .HasColumnType("bit"); + + b.Property("AssignedUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("BotTicketCreatedMessageInDmId") + .HasColumnType("decimal(20,0)"); + + b.Property("CloseReason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("ClosedDateUtc") + .HasColumnType("datetime2"); + + b.Property("CloserUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("FeedbackMessage") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("FeedbackStar") + .HasColumnType("int"); + + b.Property("InitialMessageId") + .HasColumnType("decimal(20,0)"); + + b.Property("IsForcedClosed") + .HasColumnType("bit"); + + b.Property("LastMessageDateUtc") + .HasColumnType("datetime2"); + + b.Property("ModMessageChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("OpenerUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("PrivateMessageChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketTypeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AssignedUserId"); + + b.HasIndex("CloserUserId"); + + b.HasIndex("OpenerUserId"); + + b.HasIndex("TicketTypeId"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DiscordUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("Reason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DiscordUserId") + .IsUnique(); + + b.ToTable("TicketBlacklists"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BotMessageId") + .HasColumnType("decimal(20,0)"); + + b.Property("ChangeStatus") + .HasColumnType("int"); + + b.Property("MessageContent") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageDiscordId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SenderUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("SentByMod") + .HasColumnType("bit"); + + b.Property("TicketId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("SenderUserId"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketMessages"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("FileSize") + .HasColumnType("int"); + + b.Property("Height") + .HasColumnType("int"); + + b.Property("MediaType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ProxyUrl") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Width") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageAttachments", t => + { + t.HasCheckConstraint("CK_TicketMessageAttachments_FileName_MinLength", "LEN([FileName]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_MediaType_MinLength", "LEN([MediaType]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_ProxyUrl_MinLength", "LEN([ProxyUrl]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_Url_MinLength", "LEN([Url]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("MessageContentAfter") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageContentBefore") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageHistory"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DiscordUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketNotes", t => + { + t.HasCheckConstraint("CK_TicketNotes_Content_MinLength", "LEN([Content]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmbedMessageContent") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmbedMessageTitle") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Emoji") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("TicketTypes", t => + { + t.HasCheckConstraint("CK_TicketTypes_Description_MinLength", "LEN([Description]) >= 1"); + + t.HasCheckConstraint("CK_TicketTypes_Key_MinLength", "LEN([Key]) >= 1"); + + t.HasCheckConstraint("CK_TicketTypes_Name_MinLength", "LEN([Name]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => + { + b.HasOne("Modmail.NET.Database.Entities.GuildTeam", "GuildTeam") + .WithMany("GuildTeamMembers") + .HasForeignKey("GuildTeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuildTeam"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "AssignedUser") + .WithMany("AssignedTickets") + .HasForeignKey("AssignedUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "CloserUser") + .WithMany("ClosedTickets") + .HasForeignKey("CloserUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "OpenerUser") + .WithMany("OpenedTickets") + .HasForeignKey("OpenerUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Modmail.NET.Database.Entities.TicketType", "TicketType") + .WithMany() + .HasForeignKey("TicketTypeId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("AssignedUser"); + + b.Navigation("CloserUser"); + + b.Navigation("OpenerUser"); + + b.Navigation("TicketType"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "DiscordUser") + .WithMany() + .HasForeignKey("DiscordUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscordUser"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", null) + .WithMany() + .HasForeignKey("SenderUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => + { + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", null) + .WithMany("Attachments") + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", "TicketMessage") + .WithMany("History") + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TicketMessage"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) + .WithMany("TicketNotes") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => + { + b.Navigation("AssignedTickets"); + + b.Navigation("ClosedTickets"); + + b.Navigation("OpenedTickets"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => + { + b.Navigation("GuildTeamMembers"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.Navigation("Messages"); + + b.Navigation("TicketNotes"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.Navigation("Attachments"); + + b.Navigation("History"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Modmail.NET/Database/Migrations/20250407174928_TicketMessageAddedColChangeStatus.cs b/src/Modmail.NET/Database/Migrations/20250407174928_TicketMessageAddedColChangeStatus.cs new file mode 100644 index 00000000..8c261a17 --- /dev/null +++ b/src/Modmail.NET/Database/Migrations/20250407174928_TicketMessageAddedColChangeStatus.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Modmail.NET.Database.Migrations +{ + /// + public partial class TicketMessageAddedColChangeStatus : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ChangeStatus", + table: "TicketMessages", + type: "int", + nullable: false, + defaultValue: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ChangeStatus", + table: "TicketMessages"); + } + } +} diff --git a/src/Modmail.NET/Features/Ticket/Static/TicketMessageChangeStatus.cs b/src/Modmail.NET/Features/Ticket/Static/TicketMessageChangeStatus.cs new file mode 100644 index 00000000..e0e917a1 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Static/TicketMessageChangeStatus.cs @@ -0,0 +1,8 @@ +namespace Modmail.NET.Features.Ticket.Static; + +public enum TicketMessageChangeStatus +{ + None, + Updated, + Deleted +} \ No newline at end of file From 0c4e0d98fab12ba4291910f7264e8caf943f19d6 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:12:12 +0300 Subject: [PATCH 20/47] feat(Transcript.razor): Display message status and fix message ordering in transcript page - Implemented visual indicators for edited and deleted messages in the transcript page. - Fixed a bug where messages were not grouped chronologically. - Note: Message history (previous versions of edited messages) is not yet displayed. --- .../Components/Pages/Transcript.razor | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Modmail.NET.Web.Blazor/Components/Pages/Transcript.razor b/src/Modmail.NET.Web.Blazor/Components/Pages/Transcript.razor index 434616b2..e92d46c1 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Pages/Transcript.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Pages/Transcript.razor @@ -3,6 +3,7 @@ @using Microsoft.EntityFrameworkCore @using Modmail.NET.Database.Entities @using Modmail.NET.Features.Guild.Queries +@using Modmail.NET.Features.Ticket.Static @using Modmail.NET.Features.User.Queries @using Modmail.NET.Web.Blazor.Components.Layout @using Path = Path @@ -45,6 +46,18 @@ @message.RegisterDateUtc.ToString("MM/dd/yyyy HH:mm") + "rz-color-secondary", + TicketMessageChangeStatus.Deleted => "rz-color-danger", + _ => "" + })> + @(message.ChangeStatus switch { + TicketMessageChangeStatus.Updated => " - Edited", + TicketMessageChangeStatus.Deleted => " - Deleted", + _ => "" + }) + + @@ -128,6 +141,7 @@ } + //TODO: implement message history view for edited messages, can be directly rendered or can be a dialog _id = ShortGuid.Encode(TicketId); _users = await Sender.Send(new GetDiscordUserInfoDictQuery()); _data = await _context.TicketMessages @@ -142,16 +156,18 @@ x.RegisterDateUtc.Hour, x.RegisterDateUtc.Minute, x.SenderUserId, - x.SentByMod + x.SentByMod, + x.ChangeStatus }) .Select(g => new TicketMessage { RegisterDateUtc = g.Min(x => x.RegisterDateUtc), SenderUserId = g.Key.SenderUserId, - MessageContent = string.Join("
", g.Select(x => x.MessageContent)), + MessageContent = string.Join("
", g.OrderBy(x => x.RegisterDateUtc).Select(x => x.MessageContent)), Attachments = g.SelectMany(x => x.Attachments).ToList(), // Aggregate attachments TicketId = TicketId, MessageDiscordId = 0, - SentByMod = g.Key.SentByMod + SentByMod = g.Key.SentByMod, + ChangeStatus = g.Key.ChangeStatus }) .ToArrayAsync(); From 1b7fd1538cf69228fe21dead6292d14a1a7a3bd6 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:15:36 +0300 Subject: [PATCH 21/47] feat: implement message update and delete event handlers to add message history and update message entity - Now the message entity will be updated when the content changes - Old content of the message and new content is saved and inserted into database - Updated todos --- .../Events/OnMessageDeletedEvent.cs | 35 +++++++++++-------- .../Events/OnMessageUpdatedEvent.cs | 29 ++++++++++++--- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/Modmail.NET/Features/DiscordBot/Events/OnMessageDeletedEvent.cs b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageDeletedEvent.cs index fa2e25a2..e9f7c9fe 100644 --- a/src/Modmail.NET/Features/DiscordBot/Events/OnMessageDeletedEvent.cs +++ b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageDeletedEvent.cs @@ -1,12 +1,15 @@ using DSharpPlus; using DSharpPlus.EventArgs; -using DSharpPlus.Exceptions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Modmail.NET.Common.Aspects; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Common.Utils; using Modmail.NET.Database; +using Modmail.NET.Database.Entities; +using Modmail.NET.Features.Ticket.Static; using Serilog; +using NotFoundException = DSharpPlus.Exceptions.NotFoundException; namespace Modmail.NET.Features.DiscordBot.Events; @@ -78,6 +81,8 @@ MessageDeletedEventArgs args await DeleteMirroredMessage( client, + messageEntity, + dbContext, ticket.ModMessageChannelId, messageEntity.BotMessageId ); @@ -131,8 +136,11 @@ MessageDeletedEventArgs args return; } + await DeleteMirroredMessage( client, + messageEntity, + dbContext, ticket.PrivateMessageChannelId, messageEntity.BotMessageId ); @@ -140,22 +148,19 @@ await DeleteMirroredMessage( private static async Task DeleteMirroredMessage( DiscordClient client, - ulong? channelId, - ulong? botMessageId + TicketMessage messageEntity, + ModmailDbContext dbContext, + ulong channelId, + ulong botMessageId ) { - if (!channelId.HasValue || !botMessageId.HasValue) { - Log.Warning( - "[{Source}] Cannot delete mirrored message. Invalid ChannelId or BotMessageId. ChannelId: {ChannelId}, BotMessageId: {BotMessageId}", - nameof(OnMessageDeletedEvent), - channelId, - botMessageId - ); - return; - } - try { - var channel = await client.GetChannelAsync(channelId.Value); - var message = await channel.GetMessageAsync(botMessageId.Value); + messageEntity.ChangeStatus = TicketMessageChangeStatus.Deleted; + dbContext.Update(messageEntity); + var affected = await dbContext.SaveChangesAsync(); + if (affected == 0) throw new DbInternalException(); + + var channel = await client.GetChannelAsync(channelId); + var message = await channel.GetMessageAsync(botMessageId); await message.DeleteAsync(); Log.Information( "[{Source}] Processed message deleted {ChannelId} {MessageId} " + diff --git a/src/Modmail.NET/Features/DiscordBot/Events/OnMessageUpdatedEvent.cs b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageUpdatedEvent.cs index 682b3ca0..8fa29303 100644 --- a/src/Modmail.NET/Features/DiscordBot/Events/OnMessageUpdatedEvent.cs +++ b/src/Modmail.NET/Features/DiscordBot/Events/OnMessageUpdatedEvent.cs @@ -1,17 +1,20 @@ using DSharpPlus; using DSharpPlus.Entities; using DSharpPlus.EventArgs; -using DSharpPlus.Exceptions; using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Modmail.NET.Common.Aspects; +using Modmail.NET.Common.Exceptions; using Modmail.NET.Common.Utils; using Modmail.NET.Database; +using Modmail.NET.Database.Entities; using Modmail.NET.Features.Guild.Queries; using Modmail.NET.Features.Ticket.Helpers; +using Modmail.NET.Features.Ticket.Static; using Modmail.NET.Features.User.Commands; using Serilog; +using NotFoundException = DSharpPlus.Exceptions.NotFoundException; namespace Modmail.NET.Features.DiscordBot.Events; @@ -112,6 +115,8 @@ MessageUpdatedEventArgs args await UpdateMirroredMessage( client, + messageEntity, + dbContext, scope, ticket.ModMessageChannelId, messageEntity.BotMessageId, @@ -177,6 +182,8 @@ MessageUpdatedEventArgs args await UpdateMirroredMessage( client, + messageEntity, + dbContext, scope, ticket.PrivateMessageChannelId, messageEntity.BotMessageId, @@ -188,6 +195,8 @@ await UpdateMirroredMessage( private static async Task UpdateMirroredMessage( DiscordClient client, + TicketMessage messageEntity, + ModmailDbContext dbContext, IServiceScope scope, ulong channelId, ulong messageId, @@ -196,6 +205,20 @@ private static async Task UpdateMirroredMessage( bool anonymous ) { try { + var newMessageHistory = new TicketMessageHistory { + TicketMessageId = messageEntity.Id, + MessageContentBefore = messageEntity.MessageContent, + MessageContentAfter = updatedMessage.Content + }; + + messageEntity.ChangeStatus = TicketMessageChangeStatus.Updated; + messageEntity.MessageContent = updatedMessage.Content; + dbContext.Update(messageEntity); + dbContext.Add(newMessageHistory); + + var affected = await dbContext.SaveChangesAsync(); + if (affected == 0) throw new DbInternalException(); + var sender = scope.ServiceProvider.GetRequiredService(); var option = await sender.Send(new GetGuildOptionQuery(false)); @@ -207,9 +230,7 @@ bool anonymous : TicketBotMessages.User.MessageEdited(updatedMessage, option.AlwaysAnonymous || anonymous); //TODO: Add support for removing attachment files from message on message update event - //Currently the library does not support removing single attachments, either we have to remove all of them - //Or only remove if new message has zero attachemnts but if it went from 4 to 3 then we do nothing - //The only workaround for this is to upload remaining files to discord again which is unnecessary work + //Currently discord API or library does not support attachment removal from sent message await message.ModifyAsync(x => { x.ClearEmbeds(); x.AddEmbed(embed); From 32f600c471b478116f40f3c033a30477a5d79a50 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:22:52 +0300 Subject: [PATCH 22/47] chore: version v2.3 --- src/Modmail.NET.Web.Blazor/Modmail.NET.Web.Blazor.csproj | 2 +- src/Modmail.NET/Modmail.NET.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Modmail.NET.Web.Blazor/Modmail.NET.Web.Blazor.csproj b/src/Modmail.NET.Web.Blazor/Modmail.NET.Web.Blazor.csproj index d6b7f4e4..78cd2ee7 100644 --- a/src/Modmail.NET.Web.Blazor/Modmail.NET.Web.Blazor.csproj +++ b/src/Modmail.NET.Web.Blazor/Modmail.NET.Web.Blazor.csproj @@ -5,7 +5,7 @@ disable enable 13 - 2.2.0 + 2.3.0 diff --git a/src/Modmail.NET/Modmail.NET.csproj b/src/Modmail.NET/Modmail.NET.csproj index 9ace8c6e..5fed5d2d 100644 --- a/src/Modmail.NET/Modmail.NET.csproj +++ b/src/Modmail.NET/Modmail.NET.csproj @@ -6,7 +6,7 @@ disable false 13 - 2.2.0 + 2.3.0 From 896fc319082975d1d2991a214ba857910aa4997c Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:47:30 +0300 Subject: [PATCH 23/47] fix: db migration issue when upgrading from 2.2 to 2.3 --- .../Database/Migrations/20250406180016_Constraints.Designer.cs | 2 +- .../Database/Migrations/20250406180016_Constraints.cs | 2 +- ...80146_GuildOptionAddedColStatisticsCalculateDays.Designer.cs | 2 +- .../20250406181350_GuildOptionConstraintFixMinValues.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Modmail.NET/Database/Migrations/20250406180016_Constraints.Designer.cs b/src/Modmail.NET/Database/Migrations/20250406180016_Constraints.Designer.cs index 0b13bdb1..0e296024 100644 --- a/src/Modmail.NET/Database/Migrations/20250406180016_Constraints.Designer.cs +++ b/src/Modmail.NET/Database/Migrations/20250406180016_Constraints.Designer.cs @@ -137,7 +137,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { t.HasCheckConstraint("CK_GuildOptions_Name_MinLength", "LEN([Name]) >= 1"); - t.HasCheckConstraint("CK_GuildOptions_TicketDataDeleteWaitDays_Range", "[TicketDataDeleteWaitDays] BETWEEN 1 AND 365"); + t.HasCheckConstraint("CK_GuildOptions_TicketDataDeleteWaitDays_Range", "[TicketDataDeleteWaitDays] BETWEEN -1 AND 365"); }); }); diff --git a/src/Modmail.NET/Database/Migrations/20250406180016_Constraints.cs b/src/Modmail.NET/Database/Migrations/20250406180016_Constraints.cs index cdbb546f..3d7e5c5e 100644 --- a/src/Modmail.NET/Database/Migrations/20250406180016_Constraints.cs +++ b/src/Modmail.NET/Database/Migrations/20250406180016_Constraints.cs @@ -73,7 +73,7 @@ protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder.AddCheckConstraint( name: "CK_GuildOptions_TicketDataDeleteWaitDays_Range", table: "GuildOptions", - sql: "[TicketDataDeleteWaitDays] BETWEEN 1 AND 365"); + sql: "[TicketDataDeleteWaitDays] BETWEEN -1 AND 365"); migrationBuilder.AddCheckConstraint( name: "CK_DiscordUserInfos_Username_MinLength", diff --git a/src/Modmail.NET/Database/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.Designer.cs b/src/Modmail.NET/Database/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.Designer.cs index 69f678db..4b921123 100644 --- a/src/Modmail.NET/Database/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.Designer.cs +++ b/src/Modmail.NET/Database/Migrations/20250406180146_GuildOptionAddedColStatisticsCalculateDays.Designer.cs @@ -142,7 +142,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) t.HasCheckConstraint("CK_GuildOptions_StatisticsCalculateDays_Range", "[StatisticsCalculateDays] BETWEEN 30 AND 365"); - t.HasCheckConstraint("CK_GuildOptions_TicketDataDeleteWaitDays_Range", "[TicketDataDeleteWaitDays] BETWEEN 1 AND 365"); + t.HasCheckConstraint("CK_GuildOptions_TicketDataDeleteWaitDays_Range", "[TicketDataDeleteWaitDays] BETWEEN -1 AND 365"); }); }); diff --git a/src/Modmail.NET/Database/Migrations/20250406181350_GuildOptionConstraintFixMinValues.cs b/src/Modmail.NET/Database/Migrations/20250406181350_GuildOptionConstraintFixMinValues.cs index 5ec283c9..7bb833eb 100644 --- a/src/Modmail.NET/Database/Migrations/20250406181350_GuildOptionConstraintFixMinValues.cs +++ b/src/Modmail.NET/Database/Migrations/20250406181350_GuildOptionConstraintFixMinValues.cs @@ -48,7 +48,7 @@ protected override void Down(MigrationBuilder migrationBuilder) migrationBuilder.AddCheckConstraint( name: "CK_GuildOptions_TicketDataDeleteWaitDays_Range", table: "GuildOptions", - sql: "[TicketDataDeleteWaitDays] BETWEEN 1 AND 365"); + sql: "[TicketDataDeleteWaitDays] BETWEEN -1 AND 365"); } } } From dde939cb3a9f9cfaec667eac5b08cd97cc08eb17 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 22:00:21 +0300 Subject: [PATCH 24/47] fix: issue where modal submit causing same message always --- .../DiscordBot/Events/ModalSubmittedEvent.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Modmail.NET/Features/DiscordBot/Events/ModalSubmittedEvent.cs b/src/Modmail.NET/Features/DiscordBot/Events/ModalSubmittedEvent.cs index a2e7984e..d99c8c1c 100644 --- a/src/Modmail.NET/Features/DiscordBot/Events/ModalSubmittedEvent.cs +++ b/src/Modmail.NET/Features/DiscordBot/Events/ModalSubmittedEvent.cs @@ -27,11 +27,6 @@ ModalSubmittedEventArgs args args.Interaction.Id ); - await args.Interaction.CreateResponseAsync( - DiscordInteractionResponseType.ChannelMessageWithSource, - new DiscordInteractionResponseBuilder().AsEphemeral() - .WithContent(LangProvider.This.GetTranslation(LangKeys.ThankYouForFeedback)) - ); using var scope = client.ServiceProvider.CreateScope(); var sender = scope.ServiceProvider.GetRequiredService(); @@ -45,8 +40,17 @@ await args.Interaction.CreateResponseAsync( switch (interactionName) { case "feedback": await ProcessFeedback(sender, args, parameters); + await args.Interaction.CreateResponseAsync( + DiscordInteractionResponseType.ChannelMessageWithSource, + new DiscordInteractionResponseBuilder().AsEphemeral() + .WithContent(LangProvider.This.GetTranslation(LangKeys.ThankYouForFeedback)) + ); break; case "close_ticket_with_reason": + await args.Interaction.CreateResponseAsync( + DiscordInteractionResponseType.DeferredMessageUpdate + ); + await ProcessCloseTicketWithReason(sender, args, parameters); break; default: From ea44da5bca00355d9fee09136156ade7573f8f4a Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Mon, 7 Apr 2025 22:03:35 +0300 Subject: [PATCH 25/47] fix: ticket data deletion logic --- src/Modmail.NET/Features/Ticket/Jobs/TicketDataDeleteJob.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Modmail.NET/Features/Ticket/Jobs/TicketDataDeleteJob.cs b/src/Modmail.NET/Features/Ticket/Jobs/TicketDataDeleteJob.cs index 4a5026b1..508abff0 100644 --- a/src/Modmail.NET/Features/Ticket/Jobs/TicketDataDeleteJob.cs +++ b/src/Modmail.NET/Features/Ticket/Jobs/TicketDataDeleteJob.cs @@ -6,6 +6,7 @@ using Modmail.NET.Common.Utils; using Modmail.NET.Database; using Modmail.NET.Features.Guild.Queries; +using Modmail.NET.Features.Ticket.Static; using Serilog; namespace Modmail.NET.Features.Ticket.Jobs; @@ -24,7 +25,7 @@ public override async Task Execute() { var sender = scope.ServiceProvider.GetRequiredService(); var guildOption = await sender.Send(new GetGuildOptionQuery(false)) ?? throw new NullReferenceException(); - if (guildOption.TicketDataDeleteWaitDays == -1) return; + if (guildOption.TicketDataDeleteWaitDays < TicketConstants.TicketDataDeleteWaitDaysMin) return; var dbContext = scope.ServiceProvider.GetRequiredService(); var timeoutDate = UtilDate.GetNow().AddDays(-guildOption.TicketDataDeleteWaitDays); From 4ee1d1232f166bc3b221a2597b7d71f5d697f922 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 14:06:37 +0300 Subject: [PATCH 26/47] fix: guild option incorrect range constraints --- .../Database/Entities/GuildOption.cs | 4 +- ...OptionFixConstrainMinMaxValues.Designer.cs | 684 ++++++++++++++++++ ...845_GuildOptionFixConstrainMinMaxValues.cs | 36 + .../ModmailDbContextModelSnapshot.cs | 2 +- 4 files changed, 723 insertions(+), 3 deletions(-) create mode 100644 src/Modmail.NET/Database/Migrations/20250408104845_GuildOptionFixConstrainMinMaxValues.Designer.cs create mode 100644 src/Modmail.NET/Database/Migrations/20250408104845_GuildOptionFixConstrainMinMaxValues.cs diff --git a/src/Modmail.NET/Database/Entities/GuildOption.cs b/src/Modmail.NET/Database/Entities/GuildOption.cs index 5f1800b1..df9e946b 100644 --- a/src/Modmail.NET/Database/Entities/GuildOption.cs +++ b/src/Modmail.NET/Database/Entities/GuildOption.cs @@ -28,7 +28,7 @@ public class GuildOption : IHasRegisterDate, public required ulong CategoryId { get; set; } public bool IsEnabled { get; set; } = true; - [Range(TicketConstants.TicketTimeoutMinAllowedHours, TicketConstants.TicketTimeoutMaxAllowedHours)] + [Range(-1, TicketConstants.TicketTimeoutMaxAllowedHours)] public long TicketTimeoutHours { get; set; } = -1; public bool TakeFeedbackAfterClosing { get; set; } @@ -39,7 +39,7 @@ public class GuildOption : IHasRegisterDate, [Range(-1, TicketConstants.TicketDataDeleteWaitDaysMax)] public int TicketDataDeleteWaitDays { get; set; } = -1; - [Range(-1, MetricConstants.StatisticsCalculateDaysMax)] + [Range(MetricConstants.StatisticsCalculateDaysMin, MetricConstants.StatisticsCalculateDaysMax)] public int StatisticsCalculateDays { get; set; } = MetricConstants.DefaultStatisticsCalculateDays; public TeamPermissionLevel ManageTicketMinAccessLevel { get; set; } = TeamPermissionLevel.Moderator; diff --git a/src/Modmail.NET/Database/Migrations/20250408104845_GuildOptionFixConstrainMinMaxValues.Designer.cs b/src/Modmail.NET/Database/Migrations/20250408104845_GuildOptionFixConstrainMinMaxValues.Designer.cs new file mode 100644 index 00000000..35532624 --- /dev/null +++ b/src/Modmail.NET/Database/Migrations/20250408104845_GuildOptionFixConstrainMinMaxValues.Designer.cs @@ -0,0 +1,684 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Modmail.NET.Database; + +#nullable disable + +namespace Modmail.NET.Database.Migrations +{ + [DbContext(typeof(ModmailDbContext))] + [Migration("20250408104845_GuildOptionFixConstrainMinMaxValues")] + partial class GuildOptionFixConstrainMinMaxValues + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => + { + b.Property("Id") + .HasColumnType("decimal(20,0)"); + + b.Property("AvatarUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("BannerUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Email") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Locale") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.ToTable("DiscordUserInfos", t => + { + t.HasCheckConstraint("CK_DiscordUserInfos_Username_MinLength", "LEN([Username]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildOption", b => + { + b.Property("GuildId") + .HasColumnType("decimal(20,0)"); + + b.Property("AlwaysAnonymous") + .HasColumnType("bit"); + + b.Property("BannerUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("CategoryId") + .HasColumnType("decimal(20,0)"); + + b.Property("IconUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("LogChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("ManageBlacklistMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageHangfireMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTeamsMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTicketMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTicketTypeMinAccessLevel") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PublicTranscripts") + .HasColumnType("bit"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SendTranscriptLinkToUser") + .HasColumnType("bit"); + + b.Property("StatisticsCalculateDays") + .HasColumnType("int"); + + b.Property("TakeFeedbackAfterClosing") + .HasColumnType("bit"); + + b.Property("TicketDataDeleteWaitDays") + .HasColumnType("int"); + + b.Property("TicketTimeoutHours") + .HasColumnType("bigint"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("GuildId"); + + b.ToTable("GuildOptions", t => + { + t.HasCheckConstraint("CK_GuildOptions_Name_MinLength", "LEN([Name]) >= 1"); + + t.HasCheckConstraint("CK_GuildOptions_StatisticsCalculateDays_Range", "[StatisticsCalculateDays] BETWEEN 30 AND 365"); + + t.HasCheckConstraint("CK_GuildOptions_TicketDataDeleteWaitDays_Range", "[TicketDataDeleteWaitDays] BETWEEN -1 AND 365"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowAccessToWebPanel") + .HasColumnType("bit"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PermissionLevel") + .HasColumnType("int"); + + b.Property("PingOnNewMessage") + .HasColumnType("bit"); + + b.Property("PingOnNewTicket") + .HasColumnType("bit"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("GuildTeams", t => + { + t.HasCheckConstraint("CK_GuildTeams_Name_MinLength", "LEN([Name]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("GuildTeamId") + .HasColumnType("uniqueidentifier"); + + b.Property("Key") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GuildTeamId"); + + b.ToTable("GuildTeamMembers"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Statistic", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AvgResponseTimeSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketClosedSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketsClosedPerDay") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketsOpenedPerDay") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("FastestClosedTicketSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SlowestClosedTicketSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Anonymous") + .HasColumnType("bit"); + + b.Property("AssignedUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("BotTicketCreatedMessageInDmId") + .HasColumnType("decimal(20,0)"); + + b.Property("CloseReason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("ClosedDateUtc") + .HasColumnType("datetime2"); + + b.Property("CloserUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("FeedbackMessage") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("FeedbackStar") + .HasColumnType("int"); + + b.Property("InitialMessageId") + .HasColumnType("decimal(20,0)"); + + b.Property("IsForcedClosed") + .HasColumnType("bit"); + + b.Property("LastMessageDateUtc") + .HasColumnType("datetime2"); + + b.Property("ModMessageChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("OpenerUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("PrivateMessageChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketTypeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AssignedUserId"); + + b.HasIndex("CloserUserId"); + + b.HasIndex("OpenerUserId"); + + b.HasIndex("TicketTypeId"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DiscordUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("Reason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DiscordUserId") + .IsUnique(); + + b.ToTable("TicketBlacklists"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BotMessageId") + .HasColumnType("decimal(20,0)"); + + b.Property("ChangeStatus") + .HasColumnType("int"); + + b.Property("MessageContent") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageDiscordId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SenderUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("SentByMod") + .HasColumnType("bit"); + + b.Property("TicketId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("SenderUserId"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketMessages"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("FileSize") + .HasColumnType("int"); + + b.Property("Height") + .HasColumnType("int"); + + b.Property("MediaType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ProxyUrl") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Width") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageAttachments", t => + { + t.HasCheckConstraint("CK_TicketMessageAttachments_FileName_MinLength", "LEN([FileName]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_MediaType_MinLength", "LEN([MediaType]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_ProxyUrl_MinLength", "LEN([ProxyUrl]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_Url_MinLength", "LEN([Url]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("MessageContentAfter") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageContentBefore") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageHistory"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DiscordUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketNotes", t => + { + t.HasCheckConstraint("CK_TicketNotes_Content_MinLength", "LEN([Content]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmbedMessageContent") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmbedMessageTitle") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Emoji") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("TicketTypes", t => + { + t.HasCheckConstraint("CK_TicketTypes_Description_MinLength", "LEN([Description]) >= 1"); + + t.HasCheckConstraint("CK_TicketTypes_Key_MinLength", "LEN([Key]) >= 1"); + + t.HasCheckConstraint("CK_TicketTypes_Name_MinLength", "LEN([Name]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => + { + b.HasOne("Modmail.NET.Database.Entities.GuildTeam", "GuildTeam") + .WithMany("GuildTeamMembers") + .HasForeignKey("GuildTeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuildTeam"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "AssignedUser") + .WithMany("AssignedTickets") + .HasForeignKey("AssignedUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "CloserUser") + .WithMany("ClosedTickets") + .HasForeignKey("CloserUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "OpenerUser") + .WithMany("OpenedTickets") + .HasForeignKey("OpenerUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Modmail.NET.Database.Entities.TicketType", "TicketType") + .WithMany() + .HasForeignKey("TicketTypeId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("AssignedUser"); + + b.Navigation("CloserUser"); + + b.Navigation("OpenerUser"); + + b.Navigation("TicketType"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "DiscordUser") + .WithMany() + .HasForeignKey("DiscordUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscordUser"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", null) + .WithMany() + .HasForeignKey("SenderUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => + { + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", null) + .WithMany("Attachments") + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", "TicketMessage") + .WithMany("History") + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TicketMessage"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) + .WithMany("TicketNotes") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => + { + b.Navigation("AssignedTickets"); + + b.Navigation("ClosedTickets"); + + b.Navigation("OpenedTickets"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => + { + b.Navigation("GuildTeamMembers"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.Navigation("Messages"); + + b.Navigation("TicketNotes"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.Navigation("Attachments"); + + b.Navigation("History"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Modmail.NET/Database/Migrations/20250408104845_GuildOptionFixConstrainMinMaxValues.cs b/src/Modmail.NET/Database/Migrations/20250408104845_GuildOptionFixConstrainMinMaxValues.cs new file mode 100644 index 00000000..a11f1ba8 --- /dev/null +++ b/src/Modmail.NET/Database/Migrations/20250408104845_GuildOptionFixConstrainMinMaxValues.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Modmail.NET.Database.Migrations +{ + /// + public partial class GuildOptionFixConstrainMinMaxValues : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropCheckConstraint( + name: "CK_GuildOptions_StatisticsCalculateDays_Range", + table: "GuildOptions"); + + migrationBuilder.AddCheckConstraint( + name: "CK_GuildOptions_StatisticsCalculateDays_Range", + table: "GuildOptions", + sql: "[StatisticsCalculateDays] BETWEEN 30 AND 365"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropCheckConstraint( + name: "CK_GuildOptions_StatisticsCalculateDays_Range", + table: "GuildOptions"); + + migrationBuilder.AddCheckConstraint( + name: "CK_GuildOptions_StatisticsCalculateDays_Range", + table: "GuildOptions", + sql: "[StatisticsCalculateDays] BETWEEN -1 AND 365"); + } + } +} diff --git a/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs b/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs index 6febac33..5a16de3a 100644 --- a/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs +++ b/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs @@ -137,7 +137,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { t.HasCheckConstraint("CK_GuildOptions_Name_MinLength", "LEN([Name]) >= 1"); - t.HasCheckConstraint("CK_GuildOptions_StatisticsCalculateDays_Range", "[StatisticsCalculateDays] BETWEEN -1 AND 365"); + t.HasCheckConstraint("CK_GuildOptions_StatisticsCalculateDays_Range", "[StatisticsCalculateDays] BETWEEN 30 AND 365"); t.HasCheckConstraint("CK_GuildOptions_TicketDataDeleteWaitDays_Range", "[TicketDataDeleteWaitDays] BETWEEN -1 AND 365"); }); From 47f80cc712625b8b0bf7cecf3cf42175c735af9a Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 14:08:07 +0300 Subject: [PATCH 27/47] feat: Enhance Guild Option UI with visual separators and descriptions - Improved the visual layout of the Guild Options page by adding horizontal rule (
) elements to visually separate option groups. - Added concise explanatory text to two switch controls to clarify their functionality. --- .../Components/Pages/Options.razor | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/Modmail.NET.Web.Blazor/Components/Pages/Options.razor b/src/Modmail.NET.Web.Blazor/Components/Pages/Options.razor index 2bfd09a4..d1a2c38d 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Pages/Options.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Pages/Options.razor @@ -33,13 +33,23 @@ return; } + InitGuildOptionValues(); + } + + private void InitGuildOptionValues() { if (_guildOption.TicketDataDeleteWaitDays > 0) { _enableTicketAutoDelete = true; } + else { + _guildOption.TicketDataDeleteWaitDays = TicketConstants.TicketDataDeleteWaitDaysMax; + } if (_guildOption.TicketTimeoutHours > 0) { _enableTicketTimeout = true; } + else { + _guildOption.TicketTimeoutHours = TicketConstants.TicketTimeoutMaxAllowedHours; + } } @@ -73,11 +83,14 @@ } + var dbContext = await DbContextFactory.CreateDbContextAsync(); dbContext.Update(_guildOption); var affected = await dbContext.SaveChangesAsync(); + InitGuildOptionValues(); if (affected == 0) { NotificationService.Notify(NotificationSeverity.Error, "Failed", "Guild options could not be updated"); + return; } NotificationService.Notify(NotificationSeverity.Success, "Success", "Guild options updated successfully."); @@ -125,19 +138,33 @@ +
Take feedback from user + + + After ticket is closed a message is sent to user to take text and star feedback + + + +
Force anonymous messages + + + Forces anonymous messages for all tickets, ignoring the tickets anonymous option + + +
@@ -155,9 +182,7 @@ } - - - +
@@ -183,6 +208,7 @@ } +
@@ -207,6 +233,8 @@ } +
+ +
+ Date: Tue, 8 Apr 2025 17:45:01 +0300 Subject: [PATCH 28/47] feat: implement ticket feedback data view dialog --- .../Components/Pages/Tickets.razor | 69 ++++++++++++++++--- .../wwwroot/resources/en.json | 3 +- .../Features/Ticket/Models/TicketDto.cs | 2 + src/Modmail.NET/Language/LangKeys.cs | 3 +- 4 files changed, 66 insertions(+), 11 deletions(-) diff --git a/src/Modmail.NET.Web.Blazor/Components/Pages/Tickets.razor b/src/Modmail.NET.Web.Blazor/Components/Pages/Tickets.razor index 814e5cec..2c98e5f8 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Pages/Tickets.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Pages/Tickets.razor @@ -3,12 +3,13 @@ @using Modmail.NET.Common.Exceptions @using Modmail.NET.Common.Static @using Modmail.NET.Features.Ticket.Commands +@using Modmail.NET.Features.Ticket.Mappers @using Modmail.NET.Features.Ticket.Models +@using Modmail.NET.Language @using Modmail.NET.Web.Blazor.Components.Shared.Tickets @using Modmail.NET.Web.Blazor.Providers @using Modmail.NET.Web.Blazor.Static @using Serilog -@using TicketDtoMapper = Modmail.NET.Features.Ticket.Mappers.TicketDtoMapper @inject IDbContextFactory DbContextFactory @inject TooltipService TooltipService @inject DialogService DialogService @@ -36,13 +37,14 @@ await ShowLoading(); var dbContext = await DbContextFactory.CreateDbContextAsync(); - var query = TicketDtoMapper.ProjectToDto(dbContext.Tickets - .AsNoTracking() - .Include(x => x.OpenerUser) - .Include(x => x.CloserUser) - .Include(x => x.TicketType) - .OrderByDescending(x => x.RegisterDateUtc)) - .AsQueryable(); + var query = dbContext.Tickets + .AsNoTracking() + .Include(x => x.OpenerUser) + .Include(x => x.CloserUser) + .Include(x => x.TicketType) + .OrderByDescending(x => x.RegisterDateUtc) + .ProjectToDto() + .AsQueryable(); switch (_filterTicketType) { case TicketFilterType.All: @@ -153,7 +155,43 @@ // private Task ShowAddNoteDialog(TicketDto ticketDto) { // throw new NotImplementedException(); - // } + // } @* @ticketDto.FeedbackStar *@ + + + private async Task ShowFeedback(TicketDto ticketDto) { + _ = await DialogService.OpenAsync("Ticket Feedback", + _ => + @
+ + + + @for (var i = 0; i < ticketDto.FeedbackStar; i++) { + + } + @for (var i = 0; i < 5 - ticketDto.FeedbackStar; i++) { + + } + + + + @if (string.IsNullOrEmpty(ticketDto.FeedbackMessage)) { + @LangKeys.NoFeedbackProvided.GetTranslation() + } + else { + @ticketDto.FeedbackMessage + } + + + + +
, + new DialogOptions { + Width = "500px", + CloseDialogOnEsc = true, + CloseDialogOnOverlayClick = true + }); + } + } @@ -309,6 +347,19 @@ MouseEnter="@(args => TooltipService.Open(args, "Show Notes"))" @onclick:stopPropagation="true"> + @if (data.FeedbackStar.HasValue) { + + + } @* Date: Tue, 8 Apr 2025 17:55:39 +0300 Subject: [PATCH 29/47] fix: Prevent multiple feedback submissions and remove initial feedback components - Resolved an issue where users could submit multiple feedbacks for a ticket. - The initial feedback message and associated components are now properly removed after a feedback submission. - Added `FeedbackAlreadySubmittedException` and a corresponding language key to handle cases where a user attempts to submit feedback more than once. --- src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json | 3 ++- .../Exceptions/FeedbackAlreadySubmittedException.cs | 8 ++++++++ .../Features/Ticket/Handlers/ProcessAddFeedbackHandler.cs | 8 ++++++-- src/Modmail.NET/Language/LangKeys.cs | 3 ++- 4 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 src/Modmail.NET/Common/Exceptions/FeedbackAlreadySubmittedException.cs diff --git a/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json b/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json index 8f8c3096..15148ae1 100644 --- a/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json +++ b/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json @@ -203,5 +203,6 @@ "Transcript": "Transcript", "MessageEdited": "Message Edited", "Edited": "Edited", - "NoFeedbackProvided": "No feedback provided" + "NoFeedbackProvided": "No feedback provided", + "FeedbackAlreadySubmitted": "Feedback already submitted" } diff --git a/src/Modmail.NET/Common/Exceptions/FeedbackAlreadySubmittedException.cs b/src/Modmail.NET/Common/Exceptions/FeedbackAlreadySubmittedException.cs new file mode 100644 index 00000000..ac931258 --- /dev/null +++ b/src/Modmail.NET/Common/Exceptions/FeedbackAlreadySubmittedException.cs @@ -0,0 +1,8 @@ +using Modmail.NET.Language; + +namespace Modmail.NET.Common.Exceptions; + +public class FeedbackAlreadySubmittedException : ModmailBotException +{ + public FeedbackAlreadySubmittedException() : base(LangProvider.This.GetTranslation(LangKeys.FeedbackAlreadySubmitted)) { } +} \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessAddFeedbackHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessAddFeedbackHandler.cs index 030c2e72..4cd7ce90 100644 --- a/src/Modmail.NET/Features/Ticket/Handlers/ProcessAddFeedbackHandler.cs +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessAddFeedbackHandler.cs @@ -29,7 +29,7 @@ public async Task Handle(ProcessAddFeedbackCommand request, CancellationToken ca if (!guildOption.TakeFeedbackAfterClosing) throw new InvalidOperationException("Feedback is not enabled for this guild: " + guildOption.GuildId); var ticket = await _sender.Send(new GetTicketQuery(request.TicketId, MustBeClosed: true), cancellationToken); - + if (ticket.FeedbackStar.HasValue) throw new FeedbackAlreadySubmittedException(); ticket.FeedbackStar = request.StarCount; ticket.FeedbackMessage = request.TextInput; @@ -37,6 +37,10 @@ public async Task Handle(ProcessAddFeedbackCommand request, CancellationToken ca var affected = await _dbContext.SaveChangesAsync(cancellationToken); if (affected == 0) throw new DbInternalException(); - _ = Task.Run(async () => { await request.FeedbackMessage.ModifyAsync(x => { x.AddEmbed(TicketBotMessages.User.FeedbackReceivedUpdateMessage(ticket)); }); }, cancellationToken); + await request.FeedbackMessage.ModifyAsync(x => { + x.Clear(); + x.AddEmbed(TicketBotMessages.User.FeedbackReceivedUpdateMessage(ticket)); + }); + // _ = Task.Run(async () => { }); }, cancellationToken); } } \ No newline at end of file diff --git a/src/Modmail.NET/Language/LangKeys.cs b/src/Modmail.NET/Language/LangKeys.cs index d5f0cdbd..54536762 100644 --- a/src/Modmail.NET/Language/LangKeys.cs +++ b/src/Modmail.NET/Language/LangKeys.cs @@ -206,5 +206,6 @@ public enum LangKeys Transcript, MessageEdited, Edited, - NoFeedbackProvided + NoFeedbackProvided, + FeedbackAlreadySubmitted } \ No newline at end of file From 723ff448f51f89b41bb170e27463d60c0da48dbd Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:58:49 +0300 Subject: [PATCH 30/47] fix: Make feedback content optional and enforce length limits from configuration - Made the feedback content text field optional. - Enforced maximum length constraints for feedback content and close ticket reasons, reading the values from configuration. --- src/Modmail.NET/Features/Ticket/Helpers/TicketModals.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Modmail.NET/Features/Ticket/Helpers/TicketModals.cs b/src/Modmail.NET/Features/Ticket/Helpers/TicketModals.cs index f2d2e451..11e5980a 100644 --- a/src/Modmail.NET/Features/Ticket/Helpers/TicketModals.cs +++ b/src/Modmail.NET/Features/Ticket/Helpers/TicketModals.cs @@ -1,4 +1,5 @@ using DSharpPlus.Entities; +using Modmail.NET.Common.Static; using Modmail.NET.Common.Utils; using Modmail.NET.Language; @@ -14,8 +15,8 @@ public static DiscordInteractionResponseBuilder CreateFeedbackModal(int starCoun "feedback", LangKeys.PleaseTellUsReasonsForYourRating.GetTranslation(), style: DiscordTextInputStyle.Paragraph, - min_length: 10, - max_length: 500)); + required: false, + max_length: DbLength.FeedbackMessage)); return modal; } @@ -28,7 +29,7 @@ public static DiscordInteractionResponseBuilder CreateCloseTicketWithReasonModal LangKeys.EnterReasonForClosingThisTicket.GetTranslation(), style: DiscordTextInputStyle.Paragraph, required: false, - max_length: 500)); + max_length: DbLength.Reason)); return modal; } } \ No newline at end of file From 2add4a9b293cdb531a8253fa5986cf318e71c9c9 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 18:48:11 +0300 Subject: [PATCH 31/47] feat: Implement a feedback list page - Added a data table to display all feedback entries. - All feedbacks are now viewable in a dedicated interface. --- .../Components/Layout/AuthLayout.razor | 12 +- .../Components/Pages/Feedback.razor | 141 ++++++++++++++++++ .../Ticket/Mappers/TicketFeedbackDtoMapper.cs | 12 ++ .../Ticket/Models/TicketFeedbackDto.cs | 12 ++ 4 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 src/Modmail.NET.Web.Blazor/Components/Pages/Feedback.razor create mode 100644 src/Modmail.NET/Features/Ticket/Mappers/TicketFeedbackDtoMapper.cs create mode 100644 src/Modmail.NET/Features/Ticket/Models/TicketFeedbackDto.cs diff --git a/src/Modmail.NET.Web.Blazor/Components/Layout/AuthLayout.razor b/src/Modmail.NET.Web.Blazor/Components/Layout/AuthLayout.razor index 03583603..fad1a4ae 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Layout/AuthLayout.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Layout/AuthLayout.razor @@ -36,9 +36,17 @@ + + @* *@ + @if (_hasAccessTickets) { } + + + @* *@ + + @if (_hasAccessBlacklist) { } @@ -47,13 +55,15 @@ } @if (_hasAccessTeams) { + @* *@ + } @if (_isOwner) { } @if (_hasAccessHangfire) { - + } @*
*@ diff --git a/src/Modmail.NET.Web.Blazor/Components/Pages/Feedback.razor b/src/Modmail.NET.Web.Blazor/Components/Pages/Feedback.razor new file mode 100644 index 00000000..a1a2043a --- /dev/null +++ b/src/Modmail.NET.Web.Blazor/Components/Pages/Feedback.razor @@ -0,0 +1,141 @@ +@page "/feedback" +@using Microsoft.EntityFrameworkCore +@using Modmail.NET.Features.Ticket.Mappers +@using Modmail.NET.Features.Ticket.Models +@inject IDbContextFactory DbContextFactory + +@* @attribute [AuthorizeTeam(nameof(AuthPolicy.ViewFeedbacks))] *@ + +@code { + +//TODO: Feedback page authentication + [CascadingParameter] + public Task AuthContext { get; set; } + + private IQueryable _data; + + bool _isLoading; + + async Task ShowLoading() { + _isLoading = true; + + await Task.Yield(); + + _isLoading = false; + } + + + private async Task ReloadDataAsync(LoadDataArgs args = null) { + await ShowLoading(); + + var dbContext = await DbContextFactory.CreateDbContextAsync(); + var query = dbContext.Tickets + .AsNoTracking() + .OrderByDescending(x => x.ClosedDateUtc) + .AsQueryable() + .ProjectToFeedbackDto(); + + + if (args is not null) { + query = query.ApplyDataGridFilter(args); + } + + _count = await query.CountAsync(); + + _data = args is not null + ? query.ApplyPagination(args) + : query.Skip(0).Take(10).AsQueryable(); + + StateHasChanged(); + } + + + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + await ReloadDataAsync(); + } + + + private async Task LoadDataAsync(LoadDataArgs args) { + await ReloadDataAsync(args); + } + + private int _count; + + +} + + +
+ + + + + + Feedbacks + +

+ This page shows the list of feedbacks given by users after ticket is closed. +

+ @*
*@ +
+
+ + @if (_data is null) { + + } + else { + + + + + + + + + + + + + + + + + + + } + + +
+
+
\ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Mappers/TicketFeedbackDtoMapper.cs b/src/Modmail.NET/Features/Ticket/Mappers/TicketFeedbackDtoMapper.cs new file mode 100644 index 00000000..4f1a78fb --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Mappers/TicketFeedbackDtoMapper.cs @@ -0,0 +1,12 @@ +using Modmail.NET.Features.Ticket.Models; +using Riok.Mapperly.Abstractions; + +namespace Modmail.NET.Features.Ticket.Mappers; + +[Mapper] +public static partial class TicketFeedbackDtoMapper +{ + public static partial IQueryable ProjectToFeedbackDto(this IQueryable queryable); + + public static partial TicketFeedbackDto ToFeedbackDto(this Database.Entities.Ticket entity); +} \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Models/TicketFeedbackDto.cs b/src/Modmail.NET/Features/Ticket/Models/TicketFeedbackDto.cs new file mode 100644 index 00000000..813a8b65 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Models/TicketFeedbackDto.cs @@ -0,0 +1,12 @@ +using Modmail.NET.Database.Entities; + +namespace Modmail.NET.Features.Ticket.Models; + +public class TicketFeedbackDto +{ + public required Guid Id { get; set; } + public required DateTime ClosedDateUtc { get; set; } + public required DiscordUserInfo OpenerUser { get; set; } + public int? FeedbackStar { get; set; } + public string FeedbackMessage { get; set; } +} \ No newline at end of file From f272dbed1e0c0b70b9d45e0d6c06c0653a606c5f Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 18:52:25 +0300 Subject: [PATCH 32/47] feat: Introduce tag system groundwork (database and entities) - Laid the foundation for a tag system, which will allow users to trigger pre-defined responses from the bot. - Added the `Tag` entity and updated `DbLength` with related constants. - Includes database migrations and a database snapshot. - Note: This commit provides only the groundwork; the tag functionality is not yet exposed or usable. --- src/Modmail.NET/Common/Static/DbLength.cs | 1 + src/Modmail.NET/Database/Entities/Tag.cs | 19 + .../20250408155322_TagTable.Designer.cs | 708 ++++++++++++++++++ .../Migrations/20250408155322_TagTable.cs | 37 + .../ModmailDbContextModelSnapshot.cs | 24 + src/Modmail.NET/Database/ModmailDbContext.cs | 1 + src/Modmail.NET/Modmail.NET.csproj | 1 + 7 files changed, 791 insertions(+) create mode 100644 src/Modmail.NET/Database/Entities/Tag.cs create mode 100644 src/Modmail.NET/Database/Migrations/20250408155322_TagTable.Designer.cs create mode 100644 src/Modmail.NET/Database/Migrations/20250408155322_TagTable.cs diff --git a/src/Modmail.NET/Common/Static/DbLength.cs b/src/Modmail.NET/Common/Static/DbLength.cs index d1e8db85..284bf5c6 100644 --- a/src/Modmail.NET/Common/Static/DbLength.cs +++ b/src/Modmail.NET/Common/Static/DbLength.cs @@ -16,4 +16,5 @@ public static class DbLength public const int KeyString = 128; public const int Emoji = 100; public const int Description = 1000; + public const int Tag = 32; } \ No newline at end of file diff --git a/src/Modmail.NET/Database/Entities/Tag.cs b/src/Modmail.NET/Database/Entities/Tag.cs new file mode 100644 index 00000000..68a2de97 --- /dev/null +++ b/src/Modmail.NET/Database/Entities/Tag.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; +using Modmail.NET.Common.Static; +using Modmail.NET.Database.Abstract; + +namespace Modmail.NET.Database.Entities; + +public class Tag : IHasRegisterDate, + IHasUpdateDate, + IEntity, + IGuidId +{ + [MaxLength(DbLength.Tag)] + public required string Shortcut { get; set; } + + public required string Content { get; set; } + public Guid Id { get; set; } + public DateTime RegisterDateUtc { get; set; } + public DateTime? UpdateDateUtc { get; set; } +} \ No newline at end of file diff --git a/src/Modmail.NET/Database/Migrations/20250408155322_TagTable.Designer.cs b/src/Modmail.NET/Database/Migrations/20250408155322_TagTable.Designer.cs new file mode 100644 index 00000000..c2a53b2e --- /dev/null +++ b/src/Modmail.NET/Database/Migrations/20250408155322_TagTable.Designer.cs @@ -0,0 +1,708 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Modmail.NET.Database; + +#nullable disable + +namespace Modmail.NET.Database.Migrations +{ + [DbContext(typeof(ModmailDbContext))] + [Migration("20250408155322_TagTable")] + partial class TagTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => + { + b.Property("Id") + .HasColumnType("decimal(20,0)"); + + b.Property("AvatarUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("BannerUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Email") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Locale") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.ToTable("DiscordUserInfos", t => + { + t.HasCheckConstraint("CK_DiscordUserInfos_Username_MinLength", "LEN([Username]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildOption", b => + { + b.Property("GuildId") + .HasColumnType("decimal(20,0)"); + + b.Property("AlwaysAnonymous") + .HasColumnType("bit"); + + b.Property("BannerUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("CategoryId") + .HasColumnType("decimal(20,0)"); + + b.Property("IconUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("LogChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("ManageBlacklistMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageHangfireMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTeamsMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTicketMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTicketTypeMinAccessLevel") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PublicTranscripts") + .HasColumnType("bit"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SendTranscriptLinkToUser") + .HasColumnType("bit"); + + b.Property("StatisticsCalculateDays") + .HasColumnType("int"); + + b.Property("TakeFeedbackAfterClosing") + .HasColumnType("bit"); + + b.Property("TicketDataDeleteWaitDays") + .HasColumnType("int"); + + b.Property("TicketTimeoutHours") + .HasColumnType("bigint"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("GuildId"); + + b.ToTable("GuildOptions", t => + { + t.HasCheckConstraint("CK_GuildOptions_Name_MinLength", "LEN([Name]) >= 1"); + + t.HasCheckConstraint("CK_GuildOptions_StatisticsCalculateDays_Range", "[StatisticsCalculateDays] BETWEEN 30 AND 365"); + + t.HasCheckConstraint("CK_GuildOptions_TicketDataDeleteWaitDays_Range", "[TicketDataDeleteWaitDays] BETWEEN -1 AND 365"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowAccessToWebPanel") + .HasColumnType("bit"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PermissionLevel") + .HasColumnType("int"); + + b.Property("PingOnNewMessage") + .HasColumnType("bit"); + + b.Property("PingOnNewTicket") + .HasColumnType("bit"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("GuildTeams", t => + { + t.HasCheckConstraint("CK_GuildTeams_Name_MinLength", "LEN([Name]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("GuildTeamId") + .HasColumnType("uniqueidentifier"); + + b.Property("Key") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GuildTeamId"); + + b.ToTable("GuildTeamMembers"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Statistic", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AvgResponseTimeSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketClosedSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketsClosedPerDay") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketsOpenedPerDay") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("FastestClosedTicketSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SlowestClosedTicketSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .HasColumnType("nvarchar(max)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("Shortcut") + .HasMaxLength(32) + .HasColumnType("nvarchar(32)"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Anonymous") + .HasColumnType("bit"); + + b.Property("AssignedUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("BotTicketCreatedMessageInDmId") + .HasColumnType("decimal(20,0)"); + + b.Property("CloseReason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("ClosedDateUtc") + .HasColumnType("datetime2"); + + b.Property("CloserUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("FeedbackMessage") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("FeedbackStar") + .HasColumnType("int"); + + b.Property("InitialMessageId") + .HasColumnType("decimal(20,0)"); + + b.Property("IsForcedClosed") + .HasColumnType("bit"); + + b.Property("LastMessageDateUtc") + .HasColumnType("datetime2"); + + b.Property("ModMessageChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("OpenerUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("PrivateMessageChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketTypeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AssignedUserId"); + + b.HasIndex("CloserUserId"); + + b.HasIndex("OpenerUserId"); + + b.HasIndex("TicketTypeId"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DiscordUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("Reason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DiscordUserId") + .IsUnique(); + + b.ToTable("TicketBlacklists"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BotMessageId") + .HasColumnType("decimal(20,0)"); + + b.Property("ChangeStatus") + .HasColumnType("int"); + + b.Property("MessageContent") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageDiscordId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SenderUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("SentByMod") + .HasColumnType("bit"); + + b.Property("TicketId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("SenderUserId"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketMessages"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("FileSize") + .HasColumnType("int"); + + b.Property("Height") + .HasColumnType("int"); + + b.Property("MediaType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ProxyUrl") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Width") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageAttachments", t => + { + t.HasCheckConstraint("CK_TicketMessageAttachments_FileName_MinLength", "LEN([FileName]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_MediaType_MinLength", "LEN([MediaType]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_ProxyUrl_MinLength", "LEN([ProxyUrl]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_Url_MinLength", "LEN([Url]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("MessageContentAfter") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageContentBefore") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageHistory"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DiscordUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketNotes", t => + { + t.HasCheckConstraint("CK_TicketNotes_Content_MinLength", "LEN([Content]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmbedMessageContent") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmbedMessageTitle") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Emoji") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("TicketTypes", t => + { + t.HasCheckConstraint("CK_TicketTypes_Description_MinLength", "LEN([Description]) >= 1"); + + t.HasCheckConstraint("CK_TicketTypes_Key_MinLength", "LEN([Key]) >= 1"); + + t.HasCheckConstraint("CK_TicketTypes_Name_MinLength", "LEN([Name]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => + { + b.HasOne("Modmail.NET.Database.Entities.GuildTeam", "GuildTeam") + .WithMany("GuildTeamMembers") + .HasForeignKey("GuildTeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuildTeam"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "AssignedUser") + .WithMany("AssignedTickets") + .HasForeignKey("AssignedUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "CloserUser") + .WithMany("ClosedTickets") + .HasForeignKey("CloserUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "OpenerUser") + .WithMany("OpenedTickets") + .HasForeignKey("OpenerUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Modmail.NET.Database.Entities.TicketType", "TicketType") + .WithMany() + .HasForeignKey("TicketTypeId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("AssignedUser"); + + b.Navigation("CloserUser"); + + b.Navigation("OpenerUser"); + + b.Navigation("TicketType"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "DiscordUser") + .WithMany() + .HasForeignKey("DiscordUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscordUser"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", null) + .WithMany() + .HasForeignKey("SenderUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => + { + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", null) + .WithMany("Attachments") + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", "TicketMessage") + .WithMany("History") + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TicketMessage"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) + .WithMany("TicketNotes") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => + { + b.Navigation("AssignedTickets"); + + b.Navigation("ClosedTickets"); + + b.Navigation("OpenedTickets"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => + { + b.Navigation("GuildTeamMembers"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.Navigation("Messages"); + + b.Navigation("TicketNotes"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.Navigation("Attachments"); + + b.Navigation("History"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Modmail.NET/Database/Migrations/20250408155322_TagTable.cs b/src/Modmail.NET/Database/Migrations/20250408155322_TagTable.cs new file mode 100644 index 00000000..127ddf8b --- /dev/null +++ b/src/Modmail.NET/Database/Migrations/20250408155322_TagTable.cs @@ -0,0 +1,37 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Modmail.NET.Database.Migrations +{ + /// + public partial class TagTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Tags", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Shortcut = table.Column(type: "nvarchar(32)", maxLength: 32, nullable: true), + Content = table.Column(type: "nvarchar(max)", nullable: true), + RegisterDateUtc = table.Column(type: "datetime2", nullable: false), + UpdateDateUtc = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Tags", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Tags"); + } + } +} diff --git a/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs b/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs index 5a16de3a..cb3d3771 100644 --- a/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs +++ b/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs @@ -246,6 +246,30 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Statistics"); }); + modelBuilder.Entity("Modmail.NET.Database.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .HasColumnType("nvarchar(max)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("Shortcut") + .HasMaxLength(32) + .HasColumnType("nvarchar(32)"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Tags"); + }); + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => { b.Property("Id") diff --git a/src/Modmail.NET/Database/ModmailDbContext.cs b/src/Modmail.NET/Database/ModmailDbContext.cs index cd664a75..ea663197 100644 --- a/src/Modmail.NET/Database/ModmailDbContext.cs +++ b/src/Modmail.NET/Database/ModmailDbContext.cs @@ -20,6 +20,7 @@ public ModmailDbContext(DbContextOptions options) : base(optio public DbSet TicketBlacklists { get; set; } = null!; public DbSet TicketTypes { get; set; } = null!; public DbSet Statistics { get; set; } = null!; + public DbSet Tags { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfigurationsFromAssembly(typeof(ModmailDbContext).Assembly); diff --git a/src/Modmail.NET/Modmail.NET.csproj b/src/Modmail.NET/Modmail.NET.csproj index 5fed5d2d..a8477b5f 100644 --- a/src/Modmail.NET/Modmail.NET.csproj +++ b/src/Modmail.NET/Modmail.NET.csproj @@ -77,6 +77,7 @@ + From e4202945bf45fd37f961565850faafbfec68f9ed Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 18:56:03 +0300 Subject: [PATCH 33/47] feat: Add preliminary permission policies (currently inactive) - Added a set of commented-out `AuthPolicy` definitions to `AuthPolicy.cs`, laying the groundwork for a future shift towards permission-based authorization. - These policies represent granular permissions for various features (e.g., managing Discord client, viewing metrics, managing ticket types). - Note: These policies are currently inactive and do not affect application behavior. They will be enabled in a future release. Existing role-based authorization remains in effect. --- src/Modmail.NET/Common/Static/AuthPolicy.cs | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Modmail.NET/Common/Static/AuthPolicy.cs b/src/Modmail.NET/Common/Static/AuthPolicy.cs index 912f20cc..30e10c1e 100644 --- a/src/Modmail.NET/Common/Static/AuthPolicy.cs +++ b/src/Modmail.NET/Common/Static/AuthPolicy.cs @@ -16,5 +16,32 @@ public sealed class AuthPolicy : SmartEnum public static readonly AuthPolicy ManageTeams = new(nameof(ManageTeams), 8); public static readonly AuthPolicy ManageBlacklist = new(nameof(ManageBlacklist), 9); public static readonly AuthPolicy ManageHangfire = new(nameof(ManageHangfire), 10); + + // + // public static readonly AuthPolicy ManageDiscordClient = new(nameof(ManageDiscordClient), 100); + // public static readonly AuthPolicy ViewDashboardMetrics = new(nameof(ViewDashboardMetrics), 101); + // public static readonly AuthPolicy ViewAnalytics = new(nameof(ViewAnalytics), 102); + // + // public static readonly AuthPolicy ViewTickets = new(nameof(ViewTickets), 202); + // public static readonly AuthPolicy ViewTicketTranscript = new(nameof(ViewTicketTranscript), 203); + // public static readonly AuthPolicy ViewTicketNotes = new(nameof(ViewTicketNotes), 204); + // public static readonly AuthPolicy ViewTicketFeedbacks = new(nameof(ViewTicketFeedbacks), 205); + // public static readonly AuthPolicy ViewTicketDetailMetrics = new(nameof(ViewTicketDetailMetrics), 207); + // + // public static readonly AuthPolicy BlockUser = new(nameof(BlockUser), 301); + // public static readonly AuthPolicy RemoveBlock = new(nameof(RemoveBlock), 302); + // + // public static readonly AuthPolicy ViewTicketTypes = new(nameof(ViewTicketTypes), 400); + // public static readonly AuthPolicy ManageTicketTypes = new(nameof(ManageTicketTypes), 401); + // + // public static readonly AuthPolicy ViewTeams = new(nameof(ViewTeams), 500); + // public static readonly AuthPolicy CreateTeam = new(nameof(CreateTeam), 501); + // public static readonly AuthPolicy ManageTeamsAndPermissions = new(nameof(ManageTeams), 502); + // + // public static readonly AuthPolicy ManageGuildOption = new(nameof(ManageGuildOption), 600); + // + // public static readonly AuthPolicy ManageHangfire = new(nameof(ManageHangfire), 700); + + private AuthPolicy(string name, int value) : base(name, value) { } } \ No newline at end of file From 4cd72e78497fcc51706f5a7f8761f91c8f0d24a1 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 20:27:10 +0300 Subject: [PATCH 34/47] feat: Convert ModmailBotException to concrete class - ModmailBotException is no longer an abstract class, allowing direct instantiation. - Added two public constructors that accept language keys for localized error messages. - Marked the existing protected constructor (taking two strings) as obsolete, with a descriptive message. --- .../Common/Exceptions/ModmailBotException.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Modmail.NET/Common/Exceptions/ModmailBotException.cs b/src/Modmail.NET/Common/Exceptions/ModmailBotException.cs index 5d6d2c65..5d7f64d0 100644 --- a/src/Modmail.NET/Common/Exceptions/ModmailBotException.cs +++ b/src/Modmail.NET/Common/Exceptions/ModmailBotException.cs @@ -1,15 +1,27 @@ using DSharpPlus.Entities; using Modmail.NET.Common.Static; +using Modmail.NET.Language; namespace Modmail.NET.Common.Exceptions; -public abstract class ModmailBotException : Exception +public class ModmailBotException : Exception { + [Obsolete("String message constructor is obsolete and will be removed, use constructors that takes Language keys")] protected ModmailBotException(string titleMessage, string contentMessage = null) { TitleMessage = titleMessage; ContentMessage = contentMessage; } + public ModmailBotException(LangKeys titleMessage) { + TitleMessage = titleMessage.GetTranslation(); + } + + public ModmailBotException(LangKeys titleMessage, LangKeys contentMessage) { + TitleMessage = titleMessage.GetTranslation(); + ContentMessage = contentMessage.GetTranslation(); + } + + public string TitleMessage { get; } public string ContentMessage { get; } From 488d9e4463438c8c7c2e85197ce29d6d295bcf07 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 20:31:16 +0300 Subject: [PATCH 35/47] feat: Implement tag management UI and database infrastructure - Introduced a web UI page for managing tags (create, view, update, delete). - Implemented database changes to support tags: - Added unique constraint to tag names. - Enforced non-nullability for tag name and content. - Includes database migrations. - Added necessary command handlers and language keys. - Integrated the tag management page into the web UI navigation. - Note: This implements the web UI and database components only. Tag functionality (bot commands, message calls) is not yet implemented. This is a continuation of the existing tag system groundwork. --- .../Components/Layout/AuthLayout.razor | 2 +- .../Components/Pages/Tags.razor | 250 ++++++ .../Shared/Tag/CreateOrUpdateTagDialog.razor | 121 +++ .../wwwroot/resources/en.json | 5 +- src/Modmail.NET/Common/Static/DbLength.cs | 3 +- src/Modmail.NET/Database/Entities/Tag.cs | 12 +- ...ColTitle_RenamedShortcutToName.Designer.cs | 715 +++++++++++++++++ ...ted_AddedColTitle_RenamedShortcutToName.cs | 48 ++ ...50408171908_TagNameUniqueIndex.Designer.cs | 719 +++++++++++++++++ .../20250408171908_TagNameUniqueIndex.cs | 29 + ...408171950_TagRequiredAttribute.Designer.cs | 724 ++++++++++++++++++ .../20250408171950_TagRequiredAttribute.cs | 97 +++ .../ModmailDbContextModelSnapshot.cs | 24 +- .../Tag/Commands/ProcessCreateTagCommand.cs | 6 + .../Tag/Commands/ProcessRemoveTagCommand.cs | 7 + .../Tag/Commands/ProcessUpdateTagCommand.cs | 6 + .../Tag/Handlers/ProcessCreateTagHandler.cs | 37 + .../Tag/Handlers/ProcessRemoveTagHandler.cs | 29 + .../Tag/Handlers/ProcessUpdateTagHandler.cs | 44 ++ src/Modmail.NET/Language/LangKeys.cs | 5 +- src/Modmail.NET/Modmail.NET.csproj | 8 - 21 files changed, 2873 insertions(+), 18 deletions(-) create mode 100644 src/Modmail.NET.Web.Blazor/Components/Pages/Tags.razor create mode 100644 src/Modmail.NET.Web.Blazor/Components/Shared/Tag/CreateOrUpdateTagDialog.razor create mode 100644 src/Modmail.NET/Database/Migrations/20250408162652_TagsUpdated_AddedColTitle_RenamedShortcutToName.Designer.cs create mode 100644 src/Modmail.NET/Database/Migrations/20250408162652_TagsUpdated_AddedColTitle_RenamedShortcutToName.cs create mode 100644 src/Modmail.NET/Database/Migrations/20250408171908_TagNameUniqueIndex.Designer.cs create mode 100644 src/Modmail.NET/Database/Migrations/20250408171908_TagNameUniqueIndex.cs create mode 100644 src/Modmail.NET/Database/Migrations/20250408171950_TagRequiredAttribute.Designer.cs create mode 100644 src/Modmail.NET/Database/Migrations/20250408171950_TagRequiredAttribute.cs create mode 100644 src/Modmail.NET/Features/Tag/Commands/ProcessCreateTagCommand.cs create mode 100644 src/Modmail.NET/Features/Tag/Commands/ProcessRemoveTagCommand.cs create mode 100644 src/Modmail.NET/Features/Tag/Commands/ProcessUpdateTagCommand.cs create mode 100644 src/Modmail.NET/Features/Tag/Handlers/ProcessCreateTagHandler.cs create mode 100644 src/Modmail.NET/Features/Tag/Handlers/ProcessRemoveTagHandler.cs create mode 100644 src/Modmail.NET/Features/Tag/Handlers/ProcessUpdateTagHandler.cs diff --git a/src/Modmail.NET.Web.Blazor/Components/Layout/AuthLayout.razor b/src/Modmail.NET.Web.Blazor/Components/Layout/AuthLayout.razor index fad1a4ae..b4cc4c96 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Layout/AuthLayout.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Layout/AuthLayout.razor @@ -44,7 +44,7 @@ } - @* *@ + @if (_hasAccessBlacklist) { diff --git a/src/Modmail.NET.Web.Blazor/Components/Pages/Tags.razor b/src/Modmail.NET.Web.Blazor/Components/Pages/Tags.razor new file mode 100644 index 00000000..244c3464 --- /dev/null +++ b/src/Modmail.NET.Web.Blazor/Components/Pages/Tags.razor @@ -0,0 +1,250 @@ +@page "/tags" +@using Microsoft.EntityFrameworkCore +@using Modmail.NET.Common.Exceptions +@using Modmail.NET.Common.Static +@using Modmail.NET.Database.Entities +@using Modmail.NET.Features.Tag.Commands +@using Modmail.NET.Web.Blazor.Components.Shared.Tag +@using Modmail.NET.Web.Blazor.Extensions +@using Modmail.NET.Web.Blazor.Providers +@using Serilog +@inject IDbContextFactory DbContextFactory +@inject DialogService DialogService +@inject NotificationService NotificationService +@inject TooltipService TooltipService +@inject ISender Sender +@attribute [AuthorizeTeam(nameof(AuthPolicy.ManageTicketTypes))] + + +@code { + + [CascadingParameter] + public Task AuthContext { get; set; } + + private IQueryable _data; + + bool _isLoading; + private int _count; + + async Task ShowLoading() { + _isLoading = true; + + await Task.Yield(); + + _isLoading = false; + } + + private async Task ReloadDataAsync(LoadDataArgs args = null) { + await ShowLoading(); + + var dbContext = await DbContextFactory.CreateDbContextAsync(); + var query = dbContext.Tags + .AsNoTracking() + .OrderByDescending(x => x.RegisterDateUtc) + .AsQueryable(); + + if (args is not null) { + query = query.ApplyDataGridFilter(args); + } + + _count = await query.CountAsync(); + + + _data = args is not null + ? query.ApplyPagination(args) + : query.Skip(0).Take(10).AsQueryable(); + + StateHasChanged(); + } + + + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + + await ReloadDataAsync(); + } + + + private async Task LoadDataAsync(LoadDataArgs args) { + await ReloadDataAsync(args); + } + + + private async Task RemoveAsync(Tag data) { + var dialogResult = await DialogService.Confirm("You are deleting a tag, this action can not be undone.", + "Are you sure ?", + new ConfirmOptions { + OkButtonText = "Yes", + CancelButtonText = "No", + CloseDialogOnOverlayClick = true, + CloseDialogOnEsc = true + }); + + + if (dialogResult == true) { + const string logMessage = $"[{nameof(Tag)}]{nameof(RemoveAsync)}({{@data}})"; + try { + var state = await AuthContext; + var userId = state.User.GetUserId(); + + await Sender.Send(new ProcessRemoveTagCommand(userId, data.Id)); + Log.Information(logMessage, + data); + NotificationService.Notify(NotificationSeverity.Success, + "Ticket Type deleted successfully"); + + await ReloadDataAsync(); + } + catch (ModmailBotException ex) { + Log.Warning(ex, + logMessage, + data); + ex.NotifyException(NotificationService); + } + catch (Exception ex) { + Log.Fatal(ex, + logMessage, + data); + ex.NotifyException(NotificationService); + } + } + } + + private async Task ShowAddDialog() { + var dialog = await DialogService.OpenAsync("Create Tag", + _ => + @, + new DialogOptions { + Width = "450px" + }); + if (dialog is true) { + await ReloadDataAsync(); + } + } + + private async Task ShowEditDialog(Tag data) { + var dialog = await DialogService.OpenAsync("Edit Tag", + _ => + @, + new DialogOptions { + Width = "450px" + }); + if (dialog is true) { + await ReloadDataAsync(); + } + } + +} + + +
+ + + + + + Tags + + @*
*@ +

+ Tags allow you to set short names to trigger pre-defined content responses from the bot. +

+
+
+ + @if (_data is null) { + + } + else { + + + + + + + Create Tag + + + + + + + + + + + + + + + + + + } + + +
+
+
\ No newline at end of file diff --git a/src/Modmail.NET.Web.Blazor/Components/Shared/Tag/CreateOrUpdateTagDialog.razor b/src/Modmail.NET.Web.Blazor/Components/Shared/Tag/CreateOrUpdateTagDialog.razor new file mode 100644 index 00000000..45793a44 --- /dev/null +++ b/src/Modmail.NET.Web.Blazor/Components/Shared/Tag/CreateOrUpdateTagDialog.razor @@ -0,0 +1,121 @@ +@using Modmail.NET.Common.Exceptions +@using Modmail.NET.Database.Entities +@using Modmail.NET.Features.Tag.Commands +@using Modmail.NET.Web.Blazor.Extensions +@using Serilog +@inject DialogService DialogService +@inject NotificationService NotificationService +@inject ModmailBot Bot +@inject ISender Sender + +@code { + + [CascadingParameter] + public Task AuthContext { get; set; } + + [Parameter] + public Tag Tag { get; set; } + + bool IsUpdate => Tag != null; + string _name = ""; + string _title = ""; + string _content = ""; + + protected override Task OnInitializedAsync() { + if (Tag is not null) { + _name = Tag.Name; + _title = Tag.Title; + _content = Tag.Content; + } + + return base.OnInitializedAsync(); + } + + private async Task SubmitAsync() { + if (string.IsNullOrEmpty(_name)) { + NotificationService.Notify(NotificationSeverity.Warning, "Warning", "Please enter a name."); + return; + } + + // if (string.IsNullOrEmpty(_title)) { + // NotificationService.Notify(NotificationSeverity.Warning, "Warning", "Please enter a title."); + // return; + // } + + if (string.IsNullOrEmpty(_content)) { + NotificationService.Notify(NotificationSeverity.Warning, "Warning", "Please enter a content."); + return; + } + + var dialogResult = await DialogService.Confirm("Are you sure you want to create new tag ?", + options: new ConfirmOptions { + OkButtonText = "Yes", + CancelButtonText = "No", + CloseDialogOnOverlayClick = true, + CloseDialogOnEsc = true + }); + if (dialogResult == true) { + const string logMessage = $"[{nameof(CreateOrUpdateTagDialog)}]{nameof(SubmitAsync)}({{Name}})"; + try { + var state = await AuthContext; + var userId = state.User.GetUserId(); + + if (IsUpdate && Tag is not null) { + await Sender.Send(new ProcessUpdateTagCommand(userId, Tag.Id, _name, _title, _content)); + Log.Information(logMessage, _name); + NotificationService.Notify(NotificationSeverity.Success, "Success", "Tag updated successfully."); + } + else { + await Sender.Send(new ProcessCreateTagCommand(userId, + _name, + _title, + _content)); + Log.Information(logMessage, _name); + NotificationService.Notify(NotificationSeverity.Success, "Success", "Tag created successfully."); + } + + DialogService.Close(true); + } + catch (ModmailBotException ex) { + Log.Warning(ex, logMessage, _name); + ex.NotifyException(NotificationService); + } + catch (Exception ex) { + Log.Fatal(ex, logMessage, _name); + ex.NotifyException(NotificationService); + } + } + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json b/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json index 15148ae1..c0309c92 100644 --- a/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json +++ b/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json @@ -204,5 +204,8 @@ "MessageEdited": "Message Edited", "Edited": "Edited", "NoFeedbackProvided": "No feedback provided", - "FeedbackAlreadySubmitted": "Feedback already submitted" + "FeedbackAlreadySubmitted": "Feedback already submitted", + "Tag": "Tag", + "TagWithSameNameAlreadyExists": "Tag with same name already exists", + "TagDoesntExists": "Tag doesnt exists" } diff --git a/src/Modmail.NET/Common/Static/DbLength.cs b/src/Modmail.NET/Common/Static/DbLength.cs index 284bf5c6..f44d4880 100644 --- a/src/Modmail.NET/Common/Static/DbLength.cs +++ b/src/Modmail.NET/Common/Static/DbLength.cs @@ -16,5 +16,6 @@ public static class DbLength public const int KeyString = 128; public const int Emoji = 100; public const int Description = 1000; - public const int Tag = 32; + public const int TagName = 32; + public const int TagTitle = 64; } \ No newline at end of file diff --git a/src/Modmail.NET/Database/Entities/Tag.cs b/src/Modmail.NET/Database/Entities/Tag.cs index 68a2de97..7119fa90 100644 --- a/src/Modmail.NET/Database/Entities/Tag.cs +++ b/src/Modmail.NET/Database/Entities/Tag.cs @@ -1,18 +1,26 @@ using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; using Modmail.NET.Common.Static; using Modmail.NET.Database.Abstract; namespace Modmail.NET.Database.Entities; +[Index(nameof(Name), IsUnique = true)] public class Tag : IHasRegisterDate, IHasUpdateDate, IEntity, IGuidId { - [MaxLength(DbLength.Tag)] - public required string Shortcut { get; set; } + [MaxLength(DbLength.TagName)] + [Required] + public required string Name { get; set; } + [StringLength(DbLength.TagTitle)] + public required string Title { get; set; } = null; + + [Required] public required string Content { get; set; } + public Guid Id { get; set; } public DateTime RegisterDateUtc { get; set; } public DateTime? UpdateDateUtc { get; set; } diff --git a/src/Modmail.NET/Database/Migrations/20250408162652_TagsUpdated_AddedColTitle_RenamedShortcutToName.Designer.cs b/src/Modmail.NET/Database/Migrations/20250408162652_TagsUpdated_AddedColTitle_RenamedShortcutToName.Designer.cs new file mode 100644 index 00000000..f31fae73 --- /dev/null +++ b/src/Modmail.NET/Database/Migrations/20250408162652_TagsUpdated_AddedColTitle_RenamedShortcutToName.Designer.cs @@ -0,0 +1,715 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Modmail.NET.Database; + +#nullable disable + +namespace Modmail.NET.Database.Migrations +{ + [DbContext(typeof(ModmailDbContext))] + [Migration("20250408162652_TagsUpdated_AddedColTitle_RenamedShortcutToName")] + partial class TagsUpdated_AddedColTitle_RenamedShortcutToName + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => + { + b.Property("Id") + .HasColumnType("decimal(20,0)"); + + b.Property("AvatarUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("BannerUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Email") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Locale") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.ToTable("DiscordUserInfos", t => + { + t.HasCheckConstraint("CK_DiscordUserInfos_Username_MinLength", "LEN([Username]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildOption", b => + { + b.Property("GuildId") + .HasColumnType("decimal(20,0)"); + + b.Property("AlwaysAnonymous") + .HasColumnType("bit"); + + b.Property("BannerUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("CategoryId") + .HasColumnType("decimal(20,0)"); + + b.Property("IconUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("LogChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("ManageBlacklistMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageHangfireMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTeamsMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTicketMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTicketTypeMinAccessLevel") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PublicTranscripts") + .HasColumnType("bit"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SendTranscriptLinkToUser") + .HasColumnType("bit"); + + b.Property("StatisticsCalculateDays") + .HasColumnType("int"); + + b.Property("TakeFeedbackAfterClosing") + .HasColumnType("bit"); + + b.Property("TicketDataDeleteWaitDays") + .HasColumnType("int"); + + b.Property("TicketTimeoutHours") + .HasColumnType("bigint"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("GuildId"); + + b.ToTable("GuildOptions", t => + { + t.HasCheckConstraint("CK_GuildOptions_Name_MinLength", "LEN([Name]) >= 1"); + + t.HasCheckConstraint("CK_GuildOptions_StatisticsCalculateDays_Range", "[StatisticsCalculateDays] BETWEEN 30 AND 365"); + + t.HasCheckConstraint("CK_GuildOptions_TicketDataDeleteWaitDays_Range", "[TicketDataDeleteWaitDays] BETWEEN -1 AND 365"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowAccessToWebPanel") + .HasColumnType("bit"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PermissionLevel") + .HasColumnType("int"); + + b.Property("PingOnNewMessage") + .HasColumnType("bit"); + + b.Property("PingOnNewTicket") + .HasColumnType("bit"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("GuildTeams", t => + { + t.HasCheckConstraint("CK_GuildTeams_Name_MinLength", "LEN([Name]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("GuildTeamId") + .HasColumnType("uniqueidentifier"); + + b.Property("Key") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GuildTeamId"); + + b.ToTable("GuildTeamMembers"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Statistic", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AvgResponseTimeSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketClosedSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketsClosedPerDay") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketsOpenedPerDay") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("FastestClosedTicketSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SlowestClosedTicketSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(32) + .HasColumnType("nvarchar(32)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("Title") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Tags", t => + { + t.HasCheckConstraint("CK_Tags_Title_MinLength", "LEN([Title]) >= 0"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Anonymous") + .HasColumnType("bit"); + + b.Property("AssignedUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("BotTicketCreatedMessageInDmId") + .HasColumnType("decimal(20,0)"); + + b.Property("CloseReason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("ClosedDateUtc") + .HasColumnType("datetime2"); + + b.Property("CloserUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("FeedbackMessage") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("FeedbackStar") + .HasColumnType("int"); + + b.Property("InitialMessageId") + .HasColumnType("decimal(20,0)"); + + b.Property("IsForcedClosed") + .HasColumnType("bit"); + + b.Property("LastMessageDateUtc") + .HasColumnType("datetime2"); + + b.Property("ModMessageChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("OpenerUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("PrivateMessageChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketTypeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AssignedUserId"); + + b.HasIndex("CloserUserId"); + + b.HasIndex("OpenerUserId"); + + b.HasIndex("TicketTypeId"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DiscordUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("Reason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DiscordUserId") + .IsUnique(); + + b.ToTable("TicketBlacklists"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BotMessageId") + .HasColumnType("decimal(20,0)"); + + b.Property("ChangeStatus") + .HasColumnType("int"); + + b.Property("MessageContent") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageDiscordId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SenderUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("SentByMod") + .HasColumnType("bit"); + + b.Property("TicketId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("SenderUserId"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketMessages"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("FileSize") + .HasColumnType("int"); + + b.Property("Height") + .HasColumnType("int"); + + b.Property("MediaType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ProxyUrl") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Width") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageAttachments", t => + { + t.HasCheckConstraint("CK_TicketMessageAttachments_FileName_MinLength", "LEN([FileName]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_MediaType_MinLength", "LEN([MediaType]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_ProxyUrl_MinLength", "LEN([ProxyUrl]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_Url_MinLength", "LEN([Url]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("MessageContentAfter") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageContentBefore") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageHistory"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DiscordUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketNotes", t => + { + t.HasCheckConstraint("CK_TicketNotes_Content_MinLength", "LEN([Content]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmbedMessageContent") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmbedMessageTitle") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Emoji") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("TicketTypes", t => + { + t.HasCheckConstraint("CK_TicketTypes_Description_MinLength", "LEN([Description]) >= 1"); + + t.HasCheckConstraint("CK_TicketTypes_Key_MinLength", "LEN([Key]) >= 1"); + + t.HasCheckConstraint("CK_TicketTypes_Name_MinLength", "LEN([Name]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => + { + b.HasOne("Modmail.NET.Database.Entities.GuildTeam", "GuildTeam") + .WithMany("GuildTeamMembers") + .HasForeignKey("GuildTeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuildTeam"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "AssignedUser") + .WithMany("AssignedTickets") + .HasForeignKey("AssignedUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "CloserUser") + .WithMany("ClosedTickets") + .HasForeignKey("CloserUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "OpenerUser") + .WithMany("OpenedTickets") + .HasForeignKey("OpenerUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Modmail.NET.Database.Entities.TicketType", "TicketType") + .WithMany() + .HasForeignKey("TicketTypeId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("AssignedUser"); + + b.Navigation("CloserUser"); + + b.Navigation("OpenerUser"); + + b.Navigation("TicketType"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "DiscordUser") + .WithMany() + .HasForeignKey("DiscordUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscordUser"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", null) + .WithMany() + .HasForeignKey("SenderUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => + { + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", null) + .WithMany("Attachments") + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", "TicketMessage") + .WithMany("History") + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TicketMessage"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) + .WithMany("TicketNotes") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => + { + b.Navigation("AssignedTickets"); + + b.Navigation("ClosedTickets"); + + b.Navigation("OpenedTickets"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => + { + b.Navigation("GuildTeamMembers"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.Navigation("Messages"); + + b.Navigation("TicketNotes"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.Navigation("Attachments"); + + b.Navigation("History"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Modmail.NET/Database/Migrations/20250408162652_TagsUpdated_AddedColTitle_RenamedShortcutToName.cs b/src/Modmail.NET/Database/Migrations/20250408162652_TagsUpdated_AddedColTitle_RenamedShortcutToName.cs new file mode 100644 index 00000000..d646d04a --- /dev/null +++ b/src/Modmail.NET/Database/Migrations/20250408162652_TagsUpdated_AddedColTitle_RenamedShortcutToName.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Modmail.NET.Database.Migrations +{ + /// + public partial class TagsUpdated_AddedColTitle_RenamedShortcutToName : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Shortcut", + table: "Tags", + newName: "Name"); + + migrationBuilder.AddColumn( + name: "Title", + table: "Tags", + type: "nvarchar(64)", + maxLength: 64, + nullable: true); + + migrationBuilder.AddCheckConstraint( + name: "CK_Tags_Title_MinLength", + table: "Tags", + sql: "LEN([Title]) >= 0"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropCheckConstraint( + name: "CK_Tags_Title_MinLength", + table: "Tags"); + + migrationBuilder.DropColumn( + name: "Title", + table: "Tags"); + + migrationBuilder.RenameColumn( + name: "Name", + table: "Tags", + newName: "Shortcut"); + } + } +} diff --git a/src/Modmail.NET/Database/Migrations/20250408171908_TagNameUniqueIndex.Designer.cs b/src/Modmail.NET/Database/Migrations/20250408171908_TagNameUniqueIndex.Designer.cs new file mode 100644 index 00000000..0f0f3af3 --- /dev/null +++ b/src/Modmail.NET/Database/Migrations/20250408171908_TagNameUniqueIndex.Designer.cs @@ -0,0 +1,719 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Modmail.NET.Database; + +#nullable disable + +namespace Modmail.NET.Database.Migrations +{ + [DbContext(typeof(ModmailDbContext))] + [Migration("20250408171908_TagNameUniqueIndex")] + partial class TagNameUniqueIndex + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => + { + b.Property("Id") + .HasColumnType("decimal(20,0)"); + + b.Property("AvatarUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("BannerUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Email") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Locale") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.ToTable("DiscordUserInfos", t => + { + t.HasCheckConstraint("CK_DiscordUserInfos_Username_MinLength", "LEN([Username]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildOption", b => + { + b.Property("GuildId") + .HasColumnType("decimal(20,0)"); + + b.Property("AlwaysAnonymous") + .HasColumnType("bit"); + + b.Property("BannerUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("CategoryId") + .HasColumnType("decimal(20,0)"); + + b.Property("IconUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("LogChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("ManageBlacklistMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageHangfireMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTeamsMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTicketMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTicketTypeMinAccessLevel") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PublicTranscripts") + .HasColumnType("bit"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SendTranscriptLinkToUser") + .HasColumnType("bit"); + + b.Property("StatisticsCalculateDays") + .HasColumnType("int"); + + b.Property("TakeFeedbackAfterClosing") + .HasColumnType("bit"); + + b.Property("TicketDataDeleteWaitDays") + .HasColumnType("int"); + + b.Property("TicketTimeoutHours") + .HasColumnType("bigint"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("GuildId"); + + b.ToTable("GuildOptions", t => + { + t.HasCheckConstraint("CK_GuildOptions_Name_MinLength", "LEN([Name]) >= 1"); + + t.HasCheckConstraint("CK_GuildOptions_StatisticsCalculateDays_Range", "[StatisticsCalculateDays] BETWEEN 30 AND 365"); + + t.HasCheckConstraint("CK_GuildOptions_TicketDataDeleteWaitDays_Range", "[TicketDataDeleteWaitDays] BETWEEN -1 AND 365"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowAccessToWebPanel") + .HasColumnType("bit"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PermissionLevel") + .HasColumnType("int"); + + b.Property("PingOnNewMessage") + .HasColumnType("bit"); + + b.Property("PingOnNewTicket") + .HasColumnType("bit"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("GuildTeams", t => + { + t.HasCheckConstraint("CK_GuildTeams_Name_MinLength", "LEN([Name]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("GuildTeamId") + .HasColumnType("uniqueidentifier"); + + b.Property("Key") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GuildTeamId"); + + b.ToTable("GuildTeamMembers"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Statistic", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AvgResponseTimeSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketClosedSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketsClosedPerDay") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketsOpenedPerDay") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("FastestClosedTicketSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SlowestClosedTicketSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(32) + .HasColumnType("nvarchar(32)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("Title") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasFilter("[Name] IS NOT NULL"); + + b.ToTable("Tags", t => + { + t.HasCheckConstraint("CK_Tags_Title_MinLength", "LEN([Title]) >= 0"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Anonymous") + .HasColumnType("bit"); + + b.Property("AssignedUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("BotTicketCreatedMessageInDmId") + .HasColumnType("decimal(20,0)"); + + b.Property("CloseReason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("ClosedDateUtc") + .HasColumnType("datetime2"); + + b.Property("CloserUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("FeedbackMessage") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("FeedbackStar") + .HasColumnType("int"); + + b.Property("InitialMessageId") + .HasColumnType("decimal(20,0)"); + + b.Property("IsForcedClosed") + .HasColumnType("bit"); + + b.Property("LastMessageDateUtc") + .HasColumnType("datetime2"); + + b.Property("ModMessageChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("OpenerUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("PrivateMessageChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketTypeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AssignedUserId"); + + b.HasIndex("CloserUserId"); + + b.HasIndex("OpenerUserId"); + + b.HasIndex("TicketTypeId"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DiscordUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("Reason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DiscordUserId") + .IsUnique(); + + b.ToTable("TicketBlacklists"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BotMessageId") + .HasColumnType("decimal(20,0)"); + + b.Property("ChangeStatus") + .HasColumnType("int"); + + b.Property("MessageContent") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageDiscordId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SenderUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("SentByMod") + .HasColumnType("bit"); + + b.Property("TicketId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("SenderUserId"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketMessages"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("FileSize") + .HasColumnType("int"); + + b.Property("Height") + .HasColumnType("int"); + + b.Property("MediaType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ProxyUrl") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Width") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageAttachments", t => + { + t.HasCheckConstraint("CK_TicketMessageAttachments_FileName_MinLength", "LEN([FileName]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_MediaType_MinLength", "LEN([MediaType]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_ProxyUrl_MinLength", "LEN([ProxyUrl]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_Url_MinLength", "LEN([Url]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("MessageContentAfter") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageContentBefore") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageHistory"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DiscordUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketNotes", t => + { + t.HasCheckConstraint("CK_TicketNotes_Content_MinLength", "LEN([Content]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmbedMessageContent") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmbedMessageTitle") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Emoji") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("TicketTypes", t => + { + t.HasCheckConstraint("CK_TicketTypes_Description_MinLength", "LEN([Description]) >= 1"); + + t.HasCheckConstraint("CK_TicketTypes_Key_MinLength", "LEN([Key]) >= 1"); + + t.HasCheckConstraint("CK_TicketTypes_Name_MinLength", "LEN([Name]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => + { + b.HasOne("Modmail.NET.Database.Entities.GuildTeam", "GuildTeam") + .WithMany("GuildTeamMembers") + .HasForeignKey("GuildTeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuildTeam"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "AssignedUser") + .WithMany("AssignedTickets") + .HasForeignKey("AssignedUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "CloserUser") + .WithMany("ClosedTickets") + .HasForeignKey("CloserUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "OpenerUser") + .WithMany("OpenedTickets") + .HasForeignKey("OpenerUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Modmail.NET.Database.Entities.TicketType", "TicketType") + .WithMany() + .HasForeignKey("TicketTypeId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("AssignedUser"); + + b.Navigation("CloserUser"); + + b.Navigation("OpenerUser"); + + b.Navigation("TicketType"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "DiscordUser") + .WithMany() + .HasForeignKey("DiscordUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscordUser"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", null) + .WithMany() + .HasForeignKey("SenderUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => + { + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", null) + .WithMany("Attachments") + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", "TicketMessage") + .WithMany("History") + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TicketMessage"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) + .WithMany("TicketNotes") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => + { + b.Navigation("AssignedTickets"); + + b.Navigation("ClosedTickets"); + + b.Navigation("OpenedTickets"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => + { + b.Navigation("GuildTeamMembers"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.Navigation("Messages"); + + b.Navigation("TicketNotes"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.Navigation("Attachments"); + + b.Navigation("History"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Modmail.NET/Database/Migrations/20250408171908_TagNameUniqueIndex.cs b/src/Modmail.NET/Database/Migrations/20250408171908_TagNameUniqueIndex.cs new file mode 100644 index 00000000..926f024b --- /dev/null +++ b/src/Modmail.NET/Database/Migrations/20250408171908_TagNameUniqueIndex.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Modmail.NET.Database.Migrations +{ + /// + public partial class TagNameUniqueIndex : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_Tags_Name", + table: "Tags", + column: "Name", + unique: true, + filter: "[Name] IS NOT NULL"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Tags_Name", + table: "Tags"); + } + } +} diff --git a/src/Modmail.NET/Database/Migrations/20250408171950_TagRequiredAttribute.Designer.cs b/src/Modmail.NET/Database/Migrations/20250408171950_TagRequiredAttribute.Designer.cs new file mode 100644 index 00000000..7a1fb6f4 --- /dev/null +++ b/src/Modmail.NET/Database/Migrations/20250408171950_TagRequiredAttribute.Designer.cs @@ -0,0 +1,724 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Modmail.NET.Database; + +#nullable disable + +namespace Modmail.NET.Database.Migrations +{ + [DbContext(typeof(ModmailDbContext))] + [Migration("20250408171950_TagRequiredAttribute")] + partial class TagRequiredAttribute + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => + { + b.Property("Id") + .HasColumnType("decimal(20,0)"); + + b.Property("AvatarUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("BannerUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Email") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Locale") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.ToTable("DiscordUserInfos", t => + { + t.HasCheckConstraint("CK_DiscordUserInfos_Username_MinLength", "LEN([Username]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildOption", b => + { + b.Property("GuildId") + .HasColumnType("decimal(20,0)"); + + b.Property("AlwaysAnonymous") + .HasColumnType("bit"); + + b.Property("BannerUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("CategoryId") + .HasColumnType("decimal(20,0)"); + + b.Property("IconUrl") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("LogChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("ManageBlacklistMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageHangfireMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTeamsMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTicketMinAccessLevel") + .HasColumnType("int"); + + b.Property("ManageTicketTypeMinAccessLevel") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PublicTranscripts") + .HasColumnType("bit"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SendTranscriptLinkToUser") + .HasColumnType("bit"); + + b.Property("StatisticsCalculateDays") + .HasColumnType("int"); + + b.Property("TakeFeedbackAfterClosing") + .HasColumnType("bit"); + + b.Property("TicketDataDeleteWaitDays") + .HasColumnType("int"); + + b.Property("TicketTimeoutHours") + .HasColumnType("bigint"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("GuildId"); + + b.ToTable("GuildOptions", t => + { + t.HasCheckConstraint("CK_GuildOptions_Name_MinLength", "LEN([Name]) >= 1"); + + t.HasCheckConstraint("CK_GuildOptions_StatisticsCalculateDays_Range", "[StatisticsCalculateDays] BETWEEN 30 AND 365"); + + t.HasCheckConstraint("CK_GuildOptions_TicketDataDeleteWaitDays_Range", "[TicketDataDeleteWaitDays] BETWEEN -1 AND 365"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowAccessToWebPanel") + .HasColumnType("bit"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PermissionLevel") + .HasColumnType("int"); + + b.Property("PingOnNewMessage") + .HasColumnType("bit"); + + b.Property("PingOnNewTicket") + .HasColumnType("bit"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("GuildTeams", t => + { + t.HasCheckConstraint("CK_GuildTeams_Name_MinLength", "LEN([Name]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("GuildTeamId") + .HasColumnType("uniqueidentifier"); + + b.Property("Key") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GuildTeamId"); + + b.ToTable("GuildTeamMembers"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Statistic", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AvgResponseTimeSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketClosedSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketsClosedPerDay") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("AvgTicketsOpenedPerDay") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("FastestClosedTicketSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SlowestClosedTicketSeconds") + .HasPrecision(2) + .HasColumnType("float(2)"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("nvarchar(32)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("Title") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Tags", t => + { + t.HasCheckConstraint("CK_Tags_Content_MinLength", "LEN([Content]) >= 1"); + + t.HasCheckConstraint("CK_Tags_Name_MinLength", "LEN([Name]) >= 1"); + + t.HasCheckConstraint("CK_Tags_Title_MinLength", "LEN([Title]) >= 0"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Anonymous") + .HasColumnType("bit"); + + b.Property("AssignedUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("BotTicketCreatedMessageInDmId") + .HasColumnType("decimal(20,0)"); + + b.Property("CloseReason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("ClosedDateUtc") + .HasColumnType("datetime2"); + + b.Property("CloserUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("FeedbackMessage") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("FeedbackStar") + .HasColumnType("int"); + + b.Property("InitialMessageId") + .HasColumnType("decimal(20,0)"); + + b.Property("IsForcedClosed") + .HasColumnType("bit"); + + b.Property("LastMessageDateUtc") + .HasColumnType("datetime2"); + + b.Property("ModMessageChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("OpenerUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("PrivateMessageChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketTypeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AssignedUserId"); + + b.HasIndex("CloserUserId"); + + b.HasIndex("OpenerUserId"); + + b.HasIndex("TicketTypeId"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DiscordUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("Reason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DiscordUserId") + .IsUnique(); + + b.ToTable("TicketBlacklists"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BotMessageId") + .HasColumnType("decimal(20,0)"); + + b.Property("ChangeStatus") + .HasColumnType("int"); + + b.Property("MessageContent") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageDiscordId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("SenderUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("SentByMod") + .HasColumnType("bit"); + + b.Property("TicketId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("SenderUserId"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketMessages"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("FileSize") + .HasColumnType("int"); + + b.Property("Height") + .HasColumnType("int"); + + b.Property("MediaType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ProxyUrl") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Width") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageAttachments", t => + { + t.HasCheckConstraint("CK_TicketMessageAttachments_FileName_MinLength", "LEN([FileName]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_MediaType_MinLength", "LEN([MediaType]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_ProxyUrl_MinLength", "LEN([ProxyUrl]) >= 1"); + + t.HasCheckConstraint("CK_TicketMessageAttachments_Url_MinLength", "LEN([Url]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("MessageContentAfter") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("MessageContentBefore") + .HasMaxLength(2147483647) + .HasColumnType("nvarchar(max)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketMessageId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketMessageId"); + + b.ToTable("TicketMessageHistory"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DiscordUserId") + .HasColumnType("decimal(20,0)"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("TicketId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketNotes", t => + { + t.HasCheckConstraint("CK_TicketNotes_Content_MinLength", "LEN([Content]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmbedMessageContent") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmbedMessageTitle") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Emoji") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("RegisterDateUtc") + .HasColumnType("datetime2"); + + b.Property("UpdateDateUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("TicketTypes", t => + { + t.HasCheckConstraint("CK_TicketTypes_Description_MinLength", "LEN([Description]) >= 1"); + + t.HasCheckConstraint("CK_TicketTypes_Key_MinLength", "LEN([Key]) >= 1"); + + t.HasCheckConstraint("CK_TicketTypes_Name_MinLength", "LEN([Name]) >= 1"); + }); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeamMember", b => + { + b.HasOne("Modmail.NET.Database.Entities.GuildTeam", "GuildTeam") + .WithMany("GuildTeamMembers") + .HasForeignKey("GuildTeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuildTeam"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "AssignedUser") + .WithMany("AssignedTickets") + .HasForeignKey("AssignedUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "CloserUser") + .WithMany("ClosedTickets") + .HasForeignKey("CloserUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "OpenerUser") + .WithMany("OpenedTickets") + .HasForeignKey("OpenerUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Modmail.NET.Database.Entities.TicketType", "TicketType") + .WithMany() + .HasForeignKey("TicketTypeId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("AssignedUser"); + + b.Navigation("CloserUser"); + + b.Navigation("OpenerUser"); + + b.Navigation("TicketType"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketBlacklist", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", "DiscordUser") + .WithMany() + .HasForeignKey("DiscordUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscordUser"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.HasOne("Modmail.NET.Database.Entities.DiscordUserInfo", null) + .WithMany() + .HasForeignKey("SenderUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageAttachment", b => + { + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", null) + .WithMany("Attachments") + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessageHistory", b => + { + b.HasOne("Modmail.NET.Database.Entities.TicketMessage", "TicketMessage") + .WithMany("History") + .HasForeignKey("TicketMessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TicketMessage"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketNote", b => + { + b.HasOne("Modmail.NET.Database.Entities.Ticket", null) + .WithMany("TicketNotes") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.DiscordUserInfo", b => + { + b.Navigation("AssignedTickets"); + + b.Navigation("ClosedTickets"); + + b.Navigation("OpenedTickets"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.GuildTeam", b => + { + b.Navigation("GuildTeamMembers"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => + { + b.Navigation("Messages"); + + b.Navigation("TicketNotes"); + }); + + modelBuilder.Entity("Modmail.NET.Database.Entities.TicketMessage", b => + { + b.Navigation("Attachments"); + + b.Navigation("History"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Modmail.NET/Database/Migrations/20250408171950_TagRequiredAttribute.cs b/src/Modmail.NET/Database/Migrations/20250408171950_TagRequiredAttribute.cs new file mode 100644 index 00000000..f258666a --- /dev/null +++ b/src/Modmail.NET/Database/Migrations/20250408171950_TagRequiredAttribute.cs @@ -0,0 +1,97 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Modmail.NET.Database.Migrations +{ + /// + public partial class TagRequiredAttribute : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Tags_Name", + table: "Tags"); + + migrationBuilder.AlterColumn( + name: "Name", + table: "Tags", + type: "nvarchar(32)", + maxLength: 32, + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "nvarchar(32)", + oldMaxLength: 32, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Content", + table: "Tags", + type: "nvarchar(max)", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Tags_Name", + table: "Tags", + column: "Name", + unique: true); + + migrationBuilder.AddCheckConstraint( + name: "CK_Tags_Content_MinLength", + table: "Tags", + sql: "LEN([Content]) >= 1"); + + migrationBuilder.AddCheckConstraint( + name: "CK_Tags_Name_MinLength", + table: "Tags", + sql: "LEN([Name]) >= 1"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Tags_Name", + table: "Tags"); + + migrationBuilder.DropCheckConstraint( + name: "CK_Tags_Content_MinLength", + table: "Tags"); + + migrationBuilder.DropCheckConstraint( + name: "CK_Tags_Name_MinLength", + table: "Tags"); + + migrationBuilder.AlterColumn( + name: "Name", + table: "Tags", + type: "nvarchar(32)", + maxLength: 32, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(32)", + oldMaxLength: 32); + + migrationBuilder.AlterColumn( + name: "Content", + table: "Tags", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.CreateIndex( + name: "IX_Tags_Name", + table: "Tags", + column: "Name", + unique: true, + filter: "[Name] IS NOT NULL"); + } + } +} diff --git a/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs b/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs index cb3d3771..6af6cb0d 100644 --- a/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs +++ b/src/Modmail.NET/Database/Migrations/ModmailDbContextModelSnapshot.cs @@ -253,21 +253,37 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uniqueidentifier"); b.Property("Content") + .IsRequired() .HasColumnType("nvarchar(max)"); + b.Property("Name") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("nvarchar(32)"); + b.Property("RegisterDateUtc") .HasColumnType("datetime2"); - b.Property("Shortcut") - .HasMaxLength(32) - .HasColumnType("nvarchar(32)"); + b.Property("Title") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); b.Property("UpdateDateUtc") .HasColumnType("datetime2"); b.HasKey("Id"); - b.ToTable("Tags"); + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Tags", t => + { + t.HasCheckConstraint("CK_Tags_Content_MinLength", "LEN([Content]) >= 1"); + + t.HasCheckConstraint("CK_Tags_Name_MinLength", "LEN([Name]) >= 1"); + + t.HasCheckConstraint("CK_Tags_Title_MinLength", "LEN([Title]) >= 0"); + }); }); modelBuilder.Entity("Modmail.NET.Database.Entities.Ticket", b => diff --git a/src/Modmail.NET/Features/Tag/Commands/ProcessCreateTagCommand.cs b/src/Modmail.NET/Features/Tag/Commands/ProcessCreateTagCommand.cs new file mode 100644 index 00000000..28bf61ad --- /dev/null +++ b/src/Modmail.NET/Features/Tag/Commands/ProcessCreateTagCommand.cs @@ -0,0 +1,6 @@ +using MediatR; + +namespace Modmail.NET.Features.Tag.Commands; + +//TODO: Permission check +public sealed record ProcessCreateTagCommand(ulong AuthorizedUserId, string Name, string Title, string Content) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Tag/Commands/ProcessRemoveTagCommand.cs b/src/Modmail.NET/Features/Tag/Commands/ProcessRemoveTagCommand.cs new file mode 100644 index 00000000..c80abe1f --- /dev/null +++ b/src/Modmail.NET/Features/Tag/Commands/ProcessRemoveTagCommand.cs @@ -0,0 +1,7 @@ +using MediatR; + +namespace Modmail.NET.Features.Tag.Commands; + +//TODO: Permission check +// [PermissionCheck(nameof(AuthPolicy.ManageTicketTypes))] +public sealed record ProcessRemoveTagCommand(ulong AuthorizedUserId, Guid Id) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Tag/Commands/ProcessUpdateTagCommand.cs b/src/Modmail.NET/Features/Tag/Commands/ProcessUpdateTagCommand.cs new file mode 100644 index 00000000..b6a886b1 --- /dev/null +++ b/src/Modmail.NET/Features/Tag/Commands/ProcessUpdateTagCommand.cs @@ -0,0 +1,6 @@ +using MediatR; + +namespace Modmail.NET.Features.Tag.Commands; + +//TODO: Permission check +public sealed record ProcessUpdateTagCommand(ulong AuthorizedUserId, Guid Id, string Name, string Title, string Content) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Tag/Handlers/ProcessCreateTagHandler.cs b/src/Modmail.NET/Features/Tag/Handlers/ProcessCreateTagHandler.cs new file mode 100644 index 00000000..237de251 --- /dev/null +++ b/src/Modmail.NET/Features/Tag/Handlers/ProcessCreateTagHandler.cs @@ -0,0 +1,37 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Database; +using Modmail.NET.Features.Tag.Commands; +using Modmail.NET.Language; + +namespace Modmail.NET.Features.Tag.Handlers; + +public class ProcessCreateTagHandler : IRequestHandler +{ + private readonly ModmailDbContext _dbContext; + + public ProcessCreateTagHandler(ModmailDbContext dbContext) { + _dbContext = dbContext; + } + + public async Task Handle(ProcessCreateTagCommand request, CancellationToken cancellationToken) { + var fixedName = request.Name.Trim().Replace(" ", "-").ToLower(); + + var exists = await _dbContext.Tags.Where(x => x.Name == fixedName).AnyAsync(cancellationToken: cancellationToken); + if (exists) { + throw new ModmailBotException(LangKeys.TagWithSameNameAlreadyExists); + } + + var entity = new Database.Entities.Tag { + Name = fixedName, + Title = request.Title.Trim(), + Content = request.Content.Trim(), + }; + _dbContext.Add(entity); + var affected = await _dbContext.SaveChangesAsync(cancellationToken); + if (affected == 0) throw new DbInternalException(); + + return entity; + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Features/Tag/Handlers/ProcessRemoveTagHandler.cs b/src/Modmail.NET/Features/Tag/Handlers/ProcessRemoveTagHandler.cs new file mode 100644 index 00000000..5e4d46a0 --- /dev/null +++ b/src/Modmail.NET/Features/Tag/Handlers/ProcessRemoveTagHandler.cs @@ -0,0 +1,29 @@ +using MediatR; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Database; +using Modmail.NET.Features.Tag.Commands; +using Modmail.NET.Language; + +namespace Modmail.NET.Features.Tag.Handlers; + +public class ProcessRemoveTagHandler : IRequestHandler +{ + private readonly ModmailDbContext _dbContext; + + public ProcessRemoveTagHandler(ModmailDbContext dbContext) { + _dbContext = dbContext; + } + + public async Task Handle(ProcessRemoveTagCommand request, CancellationToken cancellationToken) { + var entity = await _dbContext.Tags.FindAsync([request.Id], cancellationToken); + + if (entity is null) throw new NotFoundException(LangKeys.Tag); + + _dbContext.Remove(entity); + + var affected = await _dbContext.SaveChangesAsync(cancellationToken); + if (affected == 0) throw new DbInternalException(); + + return entity; + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Features/Tag/Handlers/ProcessUpdateTagHandler.cs b/src/Modmail.NET/Features/Tag/Handlers/ProcessUpdateTagHandler.cs new file mode 100644 index 00000000..b8598ee3 --- /dev/null +++ b/src/Modmail.NET/Features/Tag/Handlers/ProcessUpdateTagHandler.cs @@ -0,0 +1,44 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Database; +using Modmail.NET.Features.Tag.Commands; +using Modmail.NET.Language; + +namespace Modmail.NET.Features.Tag.Handlers; + +public class ProcessUpdateTagHandler : IRequestHandler +{ + private readonly ModmailDbContext _dbContext; + + public ProcessUpdateTagHandler(ModmailDbContext dbContext) { + _dbContext = dbContext; + } + + public async Task Handle(ProcessUpdateTagCommand request, CancellationToken cancellationToken) { + var fixedName = request.Name.Trim().Replace(" ", "-").ToLower(); + + var entity = await _dbContext.Tags.Where(x => x.Name == fixedName).FirstOrDefaultAsync(cancellationToken: cancellationToken); + if (entity is null) { + throw new ModmailBotException(LangKeys.TagDoesntExists); + } + + var isNameSame = fixedName.Equals(entity.Name, StringComparison.InvariantCultureIgnoreCase); + if (!isNameSame) { + var exists = await _dbContext.Tags.Where(x => x.Name == fixedName).AnyAsync(cancellationToken: cancellationToken); + if (exists) { + throw new ModmailBotException(LangKeys.TagWithSameNameAlreadyExists); + } + } + + entity.Title = request.Title.Trim(); + entity.Content = request.Content.Trim(); + entity.Name = fixedName; + + _dbContext.Update(entity); + var affected = await _dbContext.SaveChangesAsync(cancellationToken); + if (affected == 0) throw new DbInternalException(); + + return entity; + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Language/LangKeys.cs b/src/Modmail.NET/Language/LangKeys.cs index 54536762..b4380795 100644 --- a/src/Modmail.NET/Language/LangKeys.cs +++ b/src/Modmail.NET/Language/LangKeys.cs @@ -207,5 +207,8 @@ public enum LangKeys MessageEdited, Edited, NoFeedbackProvided, - FeedbackAlreadySubmitted + FeedbackAlreadySubmitted, + Tag, + TagWithSameNameAlreadyExists, + TagDoesntExists } \ No newline at end of file diff --git a/src/Modmail.NET/Modmail.NET.csproj b/src/Modmail.NET/Modmail.NET.csproj index a8477b5f..2a643dd6 100644 --- a/src/Modmail.NET/Modmail.NET.csproj +++ b/src/Modmail.NET/Modmail.NET.csproj @@ -75,12 +75,4 @@
- - - - - - - - From f924495d678355fa08ea3eff683a883391a6a56a Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:04:57 +0300 Subject: [PATCH 36/47] feat: implement CheckActiveTicket command --- .../Handlers/CheckActiveTicketHandler.cs | 19 +++++++++++++++++++ .../Ticket/Queries/CheckActiveTicketQuery.cs | 5 +++++ 2 files changed, 24 insertions(+) create mode 100644 src/Modmail.NET/Features/Ticket/Handlers/CheckActiveTicketHandler.cs create mode 100644 src/Modmail.NET/Features/Ticket/Queries/CheckActiveTicketQuery.cs diff --git a/src/Modmail.NET/Features/Ticket/Handlers/CheckActiveTicketHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/CheckActiveTicketHandler.cs new file mode 100644 index 00000000..ad61c61d --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Handlers/CheckActiveTicketHandler.cs @@ -0,0 +1,19 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Modmail.NET.Database; +using Modmail.NET.Features.Ticket.Queries; + +namespace Modmail.NET.Features.Ticket.Handlers; + +public class CheckActiveTicketHandler : IRequestHandler +{ + private readonly ModmailDbContext _dbContext; + + public CheckActiveTicketHandler(ModmailDbContext dbContext) { + _dbContext = dbContext; + } + + public async Task Handle(CheckActiveTicketQuery request, CancellationToken cancellationToken) { + return await _dbContext.Tickets.Where(x => x.Id == request.TicketId && !x.ClosedDateUtc.HasValue).AnyAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Queries/CheckActiveTicketQuery.cs b/src/Modmail.NET/Features/Ticket/Queries/CheckActiveTicketQuery.cs new file mode 100644 index 00000000..c4260373 --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Queries/CheckActiveTicketQuery.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace Modmail.NET.Features.Ticket.Queries; + +public sealed record CheckActiveTicketQuery(Guid TicketId) : IRequest; \ No newline at end of file From 9676675102795bf102f2234446e23408107aaec1 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:05:18 +0300 Subject: [PATCH 37/47] feat: added new language keys for tag system --- src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json | 5 ++++- src/Modmail.NET/Language/LangKeys.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json b/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json index c0309c92..b4146b02 100644 --- a/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json +++ b/src/Modmail.NET.Web.Blazor/wwwroot/resources/en.json @@ -207,5 +207,8 @@ "FeedbackAlreadySubmitted": "Feedback already submitted", "Tag": "Tag", "TagWithSameNameAlreadyExists": "Tag with same name already exists", - "TagDoesntExists": "Tag doesnt exists" + "TagDoesntExists": "Tag doesnt exists", + "TagCreatedSuccessfully": "Tag created successfully", + "TagRemovedSuccessfully": "Tag removed successfully", + "TagUpdatedSuccessfully": "Tag updated successfully" } diff --git a/src/Modmail.NET/Language/LangKeys.cs b/src/Modmail.NET/Language/LangKeys.cs index b4380795..696bbee7 100644 --- a/src/Modmail.NET/Language/LangKeys.cs +++ b/src/Modmail.NET/Language/LangKeys.cs @@ -210,5 +210,8 @@ public enum LangKeys FeedbackAlreadySubmitted, Tag, TagWithSameNameAlreadyExists, - TagDoesntExists + TagDoesntExists, + TagCreatedSuccessfully, + TagRemovedSuccessfully, + TagUpdatedSuccessfully } \ No newline at end of file From ae2773e80fd1da9dcf9f70c83fed2bd2f2bd68fb Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:05:27 +0300 Subject: [PATCH 38/47] feat: added new TagReceivedColor --- src/Modmail.NET/Common/Static/ModmailColors.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Modmail.NET/Common/Static/ModmailColors.cs b/src/Modmail.NET/Common/Static/ModmailColors.cs index 9021de78..1f2fcbd3 100644 --- a/src/Modmail.NET/Common/Static/ModmailColors.cs +++ b/src/Modmail.NET/Common/Static/ModmailColors.cs @@ -13,6 +13,7 @@ public static class ModmailColors //Message public static readonly DiscordColor MessageReceivedColor = DiscordColor.Blue; + public static readonly DiscordColor TagReceivedColor = DiscordColor.Blue; //Ticket public static readonly DiscordColor TicketCreatedColor = DiscordColor.Blue; From 826b01ce3571d8b49e9c6502899af79d2ce8fc74 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:06:46 +0300 Subject: [PATCH 39/47] feat(TagProvider): Implement Discord slash command provider for tags - Introduced a `TagProvider` class to dynamically generate slash commands for available tags. - This allows users to access tags directly through Discord slash commands. --- .../DiscordCommands/Helpers/TagProvider.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/Modmail.NET/Features/DiscordCommands/Helpers/TagProvider.cs diff --git a/src/Modmail.NET/Features/DiscordCommands/Helpers/TagProvider.cs b/src/Modmail.NET/Features/DiscordCommands/Helpers/TagProvider.cs new file mode 100644 index 00000000..abdace48 --- /dev/null +++ b/src/Modmail.NET/Features/DiscordCommands/Helpers/TagProvider.cs @@ -0,0 +1,26 @@ +using DSharpPlus.Commands.Processors.SlashCommands; +using DSharpPlus.Commands.Processors.SlashCommands.ArgumentModifiers; +using DSharpPlus.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using Modmail.NET.Database; + +namespace Modmail.NET.Features.DiscordCommands.Helpers; + +public class TagProvider : IAutoCompleteProvider +{ + public async ValueTask> AutoCompleteAsync(AutoCompleteContext context) { + const string cacheKey = "TagProvider.Provider.AutoComplete"; + var cache = context.ServiceProvider.GetRequiredService(); + return await cache.GetOrCreateAsync(cacheKey, Get, new MemoryCacheEntryOptions { + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(5) + }); + + async Task> Get(ICacheEntry entry) { + var scope = context.ServiceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + return await dbContext.Tags.Select(x => new DiscordAutoCompleteChoice(x.Name, x.Name)).ToArrayAsync(); + } + } +} \ No newline at end of file From d120caa3d39b5ec40acdeba889c254a748bdace8 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:07:22 +0300 Subject: [PATCH 40/47] feat(TagBotMessages): Create helper class for tag message generation - Added a `TagBotMessages` helper class to centralize the creation of messages sent by the bot when using tags. - This improves code organization and consistency in tag-related responses. --- .../Features/Tag/Helpers/TagBotMessages.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/Modmail.NET/Features/Tag/Helpers/TagBotMessages.cs diff --git a/src/Modmail.NET/Features/Tag/Helpers/TagBotMessages.cs b/src/Modmail.NET/Features/Tag/Helpers/TagBotMessages.cs new file mode 100644 index 00000000..fd5c9f3b --- /dev/null +++ b/src/Modmail.NET/Features/Tag/Helpers/TagBotMessages.cs @@ -0,0 +1,38 @@ +using DSharpPlus.Entities; +using Modmail.NET.Common.Extensions; +using Modmail.NET.Common.Static; + +namespace Modmail.NET.Features.Tag.Helpers; + +public static class TagBotMessages +{ + public static DiscordMessageBuilder TagSent(Database.Entities.Tag message) { + var embed = new DiscordEmbedBuilder() + .WithTitle(message.Title) + .WithDescription(message.Content) + .WithGuildInfoFooter() + .WithCustomTimestamp() + .WithColor(ModmailColors.TagReceivedColor); + + var msg = new DiscordMessageBuilder(); + msg.AddEmbed(embed); + return msg; + } + + public static DiscordMessageBuilder TagReceivedToTicket(Database.Entities.Tag message, DiscordUser author = null, bool anonymous = false) { + var embed = new DiscordEmbedBuilder() + .WithTitle(message.Title) + .WithDescription(message.Content) + .WithGuildInfoFooter() + .WithCustomTimestamp() + .WithColor(ModmailColors.TagReceivedColor); + + if (author is not null) + if (!anonymous) + embed.WithUserAsAuthor(author); + + var msg = new DiscordMessageBuilder(); + msg.AddEmbed(embed); + return msg; + } +} \ No newline at end of file From fd738d594659ede1b15cbd172cac0f4b9dded94a Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:08:34 +0300 Subject: [PATCH 41/47] feat: Implement handlers and commands for tag retrieval and execution - Added `GetTagByNameHandler` to retrieve tag data by name. - Added `ProcessTagSendMessageHandler` to handle sending tag content to Discord. --- .../Tag/Handlers/GetTagByNameHandler.cs | 24 +++++++ .../Features/Tag/Queries/GetTagByNameQuery.cs | 5 ++ .../Commands/ProcessTagSendMessageCommand.cs | 12 ++++ .../Handlers/ProcessTagSendMessageHandler.cs | 63 +++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 src/Modmail.NET/Features/Tag/Handlers/GetTagByNameHandler.cs create mode 100644 src/Modmail.NET/Features/Tag/Queries/GetTagByNameQuery.cs create mode 100644 src/Modmail.NET/Features/Ticket/Commands/ProcessTagSendMessageCommand.cs create mode 100644 src/Modmail.NET/Features/Ticket/Handlers/ProcessTagSendMessageHandler.cs diff --git a/src/Modmail.NET/Features/Tag/Handlers/GetTagByNameHandler.cs b/src/Modmail.NET/Features/Tag/Handlers/GetTagByNameHandler.cs new file mode 100644 index 00000000..ad67f326 --- /dev/null +++ b/src/Modmail.NET/Features/Tag/Handlers/GetTagByNameHandler.cs @@ -0,0 +1,24 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Database; +using Modmail.NET.Features.Tag.Queries; +using Modmail.NET.Language; + +namespace Modmail.NET.Features.Tag.Handlers; + +public class GetTagByNameHandler : IRequestHandler +{ + private readonly ModmailDbContext _dbContext; + + public GetTagByNameHandler(ModmailDbContext dbContext) { + _dbContext = dbContext; + } + + public async Task Handle(GetTagByNameQuery request, CancellationToken cancellationToken) { + var tag = await _dbContext.Tags.Where(x => x.Name == request.Name).FirstOrDefaultAsync(cancellationToken); + if (tag is null) throw new ModmailBotException(LangKeys.TagDoesntExists); + + return tag; + } +} \ No newline at end of file diff --git a/src/Modmail.NET/Features/Tag/Queries/GetTagByNameQuery.cs b/src/Modmail.NET/Features/Tag/Queries/GetTagByNameQuery.cs new file mode 100644 index 00000000..0a27ddf1 --- /dev/null +++ b/src/Modmail.NET/Features/Tag/Queries/GetTagByNameQuery.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace Modmail.NET.Features.Tag.Queries; + +public sealed record GetTagByNameQuery(string Name) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Commands/ProcessTagSendMessageCommand.cs b/src/Modmail.NET/Features/Ticket/Commands/ProcessTagSendMessageCommand.cs new file mode 100644 index 00000000..8f2f55bc --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Commands/ProcessTagSendMessageCommand.cs @@ -0,0 +1,12 @@ +using DSharpPlus.Entities; +using MediatR; + +namespace Modmail.NET.Features.Ticket.Commands; + +public sealed record ProcessTagSendMessageCommand( + Guid TicketId, + Guid TagId, + DiscordUser ModUser, + DiscordChannel Channel, + DiscordGuild Guild +) : IRequest; \ No newline at end of file diff --git a/src/Modmail.NET/Features/Ticket/Handlers/ProcessTagSendMessageHandler.cs b/src/Modmail.NET/Features/Ticket/Handlers/ProcessTagSendMessageHandler.cs new file mode 100644 index 00000000..094d1ace --- /dev/null +++ b/src/Modmail.NET/Features/Ticket/Handlers/ProcessTagSendMessageHandler.cs @@ -0,0 +1,63 @@ +using MediatR; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Utils; +using Modmail.NET.Database; +using Modmail.NET.Features.Guild.Queries; +using Modmail.NET.Features.Tag.Helpers; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Queries; +using Modmail.NET.Language; +using TicketMessage = Modmail.NET.Database.Entities.TicketMessage; + +namespace Modmail.NET.Features.Ticket.Handlers; + +public class ProcessTagSendMessageHandler : IRequestHandler +{ + private readonly ModmailBot _bot; + private readonly ModmailDbContext _dbContext; + private readonly ISender _sender; + + public ProcessTagSendMessageHandler(ISender sender, + ModmailBot bot, + ModmailDbContext dbContext) { + _sender = sender; + _bot = bot; + _dbContext = dbContext; + } + + public async Task Handle(ProcessTagSendMessageCommand request, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(request.ModUser); + ArgumentNullException.ThrowIfNull(request.Channel); + ArgumentNullException.ThrowIfNull(request.Guild); + + var guildOption = await _sender.Send(new GetGuildOptionQuery(false), cancellationToken); + + var ticket = await _sender.Send(new GetTicketQuery(request.TicketId, MustBeOpen: true), cancellationToken); + ticket.LastMessageDateUtc = UtilDate.GetNow(); + + _dbContext.Update(ticket); + + + var privateChannel = await _bot.Client.GetChannelAsync(ticket.PrivateMessageChannelId); + + var tag = await _dbContext.Tags.FindAsync([request.TagId], cancellationToken); + if (tag is null) throw new ModmailBotException(LangKeys.TagDoesntExists); + + var ticketMessage = new TicketMessage { + SenderUserId = request.ModUser.Id, + MessageDiscordId = 0, + TicketId = request.TicketId, + SentByMod = true, + MessageContent = "TagCommand:" + tag.Name //TODO: Find a way to render tags or directly insert tag content to ticket message logs + }; + + var tagMessage = TagBotMessages.TagReceivedToTicket(tag, request.ModUser, ticket.Anonymous || guildOption.AlwaysAnonymous); + var botMessage = await privateChannel.SendMessageAsync(tagMessage); + + ticketMessage.BotMessageId = botMessage.Id; + await _dbContext.AddAsync(ticketMessage, cancellationToken); + + var affected = await _dbContext.SaveChangesAsync(cancellationToken); + if (affected == 0) throw new DbInternalException(); + } +} \ No newline at end of file From ae33f0690be50fc6ef68a71d6d138fac8b908be4 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:09:30 +0300 Subject: [PATCH 42/47] feat: Enable 'tag' slash command for tag retrieval and execution - Implemented a `tag` slash command, allowing users to trigger tag responses from the bot. - Registered `TagSlashCommands.cs` with the Discord bot for slash command availability. --- .../Dependency/DiscordBotDependency.cs | 1 + .../Handlers/TagSlashCommands.cs | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/Modmail.NET/Features/DiscordCommands/Handlers/TagSlashCommands.cs diff --git a/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs b/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs index be38d661..d97f753e 100644 --- a/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs +++ b/src/Modmail.NET.Web.Blazor/Dependency/DiscordBotDependency.cs @@ -25,6 +25,7 @@ public static void Configure(WebApplicationBuilder builder) { extension.AddCommands(); extension.AddCommands(); extension.AddCommands(); + extension.AddCommands(); extension.AddChecks(typeof(ModmailBotProjectMarker).Assembly); TextCommandProcessor textCommandProcessor = new(new TextCommandConfiguration { diff --git a/src/Modmail.NET/Features/DiscordCommands/Handlers/TagSlashCommands.cs b/src/Modmail.NET/Features/DiscordCommands/Handlers/TagSlashCommands.cs new file mode 100644 index 00000000..5e835d41 --- /dev/null +++ b/src/Modmail.NET/Features/DiscordCommands/Handlers/TagSlashCommands.cs @@ -0,0 +1,62 @@ +using System.ComponentModel; +using DSharpPlus.Commands; +using DSharpPlus.Commands.Processors.SlashCommands; +using DSharpPlus.Commands.Processors.SlashCommands.ArgumentModifiers; +using DSharpPlus.Entities; +using MediatR; +using Modmail.NET.Common.Aspects; +using Modmail.NET.Common.Exceptions; +using Modmail.NET.Common.Extensions; +using Modmail.NET.Common.Utils; +using Modmail.NET.Features.DiscordCommands.Checks.Attributes; +using Modmail.NET.Features.DiscordCommands.Helpers; +using Modmail.NET.Features.Tag.Helpers; +using Modmail.NET.Features.Tag.Queries; +using Modmail.NET.Features.Ticket.Commands; +using Modmail.NET.Features.Ticket.Queries; +using Serilog; + +namespace Modmail.NET.Features.DiscordCommands.Handlers; + +public class TagSlashCommands +{ + private readonly ISender _sender; + + public TagSlashCommands(ISender sender) { + _sender = sender; + } + + [Command("tag")] + [Description("Get tag content")] + [PerformanceLoggerAspect] + [UpdateUserInformation] + public async Task Get(SlashCommandContext ctx, + [Parameter("name")] [Description("Tag name")] [SlashAutoCompleteProvider(typeof(TagProvider))] + string name + ) { + const string logMessage = $"[{nameof(TagSlashCommands)}]{nameof(Get)}({{ContextUserId}},{{TagName}})"; + await ctx.Interaction.CreateResponseAsync(DiscordInteractionResponseType.DeferredChannelMessageWithSource, new DiscordInteractionResponseBuilder()); + try { + var tag = await _sender.Send(new GetTagByNameQuery(name)); + + await ctx.EditResponseAsync(TagBotMessages.TagSent(tag)); + + var channelTopic = ctx.Channel.Topic; + var ticketId = UtilChannelTopic.GetTicketIdFromChannelTopic(channelTopic); + if (ticketId != Guid.Empty) { + var isActiveTicket = await _sender.Send(new CheckActiveTicketQuery(ticketId)); + if (isActiveTicket) await _sender.Send(new ProcessTagSendMessageCommand(ticketId, tag.Id, ctx.User, ctx.Channel, ctx.Guild)); + } + + Log.Information(logMessage, ctx.User.Id, name); + } + catch (ModmailBotException ex) { + await ctx.EditResponseAsync(ex.ToWebhookResponse()); + Log.Warning(ex, logMessage, ctx.User.Id, name); + } + catch (Exception ex) { + await ctx.EditResponseAsync(ex.ToWebhookResponse()); + Log.Fatal(ex, logMessage, ctx.User.Id, name); + } + } +} \ No newline at end of file From 8b6eedf55f8541621d405d072fe300164db20028 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:10:36 +0300 Subject: [PATCH 43/47] fix: Prevent spaces in tag names through UI conversion - Implemented automatic conversion of spaces to hyphens (-) in tag names within the UI. - This ensures tag names are consistent and avoids issues caused by spaces. --- .../Shared/Tag/CreateOrUpdateTagDialog.razor | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Modmail.NET.Web.Blazor/Components/Shared/Tag/CreateOrUpdateTagDialog.razor b/src/Modmail.NET.Web.Blazor/Components/Shared/Tag/CreateOrUpdateTagDialog.razor index 45793a44..3667cadc 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Shared/Tag/CreateOrUpdateTagDialog.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Shared/Tag/CreateOrUpdateTagDialog.razor @@ -1,4 +1,5 @@ @using Modmail.NET.Common.Exceptions +@using Modmail.NET.Common.Static @using Modmail.NET.Database.Entities @using Modmail.NET.Features.Tag.Commands @using Modmail.NET.Web.Blazor.Extensions @@ -18,6 +19,12 @@ bool IsUpdate => Tag != null; string _name = ""; + + private string FixedName { + get => _name; + set => _name = value.Trim().Replace(" ", "-"); + } + string _title = ""; string _content = ""; @@ -87,6 +94,7 @@ } } + } @@ -94,13 +102,13 @@ - + - + From 0188573c4981791931039631391091f17164621b Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:41:56 +0300 Subject: [PATCH 44/47] fix: Create or Update dialog confirmation message --- .../Components/Shared/Tag/CreateOrUpdateTagDialog.razor | 4 +++- .../Components/Shared/Teams/CreateOrUpdateTeamDialog.razor | 4 +++- .../Shared/TicketType/CreateOrUpdateTicketTypeDialog.razor | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Modmail.NET.Web.Blazor/Components/Shared/Tag/CreateOrUpdateTagDialog.razor b/src/Modmail.NET.Web.Blazor/Components/Shared/Tag/CreateOrUpdateTagDialog.razor index 3667cadc..98db3b30 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Shared/Tag/CreateOrUpdateTagDialog.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Shared/Tag/CreateOrUpdateTagDialog.razor @@ -54,7 +54,9 @@ return; } - var dialogResult = await DialogService.Confirm("Are you sure you want to create new tag ?", + var dialogResult = await DialogService.Confirm(IsUpdate + ? "Are you sure you want to update this tag ?" + : "Are you sure you want to create new tag ?", options: new ConfirmOptions { OkButtonText = "Yes", CancelButtonText = "No", diff --git a/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/CreateOrUpdateTeamDialog.razor b/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/CreateOrUpdateTeamDialog.razor index 2a5f25a4..ed4308a0 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/CreateOrUpdateTeamDialog.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Shared/Teams/CreateOrUpdateTeamDialog.razor @@ -71,7 +71,9 @@ return; } - var dialogResult = await DialogService.Confirm("Are you sure you want to create new team ?", + var dialogResult = await DialogService.Confirm(IsUpdate + ? "Are you sure you want to update this team ?" + : "Are you sure you want to create new team ?", options: new ConfirmOptions { OkButtonText = "Yes", CancelButtonText = "No", diff --git a/src/Modmail.NET.Web.Blazor/Components/Shared/TicketType/CreateOrUpdateTicketTypeDialog.razor b/src/Modmail.NET.Web.Blazor/Components/Shared/TicketType/CreateOrUpdateTicketTypeDialog.razor index 788f140a..2d0be4a0 100644 --- a/src/Modmail.NET.Web.Blazor/Components/Shared/TicketType/CreateOrUpdateTicketTypeDialog.razor +++ b/src/Modmail.NET.Web.Blazor/Components/Shared/TicketType/CreateOrUpdateTicketTypeDialog.razor @@ -66,7 +66,9 @@ : _emoji; - var dialogResult = await DialogService.Confirm("Are you sure you want to create new ticket type ?", + var dialogResult = await DialogService.Confirm(IsUpdate + ? "Are you sure you want to update this ticket type ?" + : "Are you sure you want to create new ticket type ?", options: new ConfirmOptions { OkButtonText = "Yes", CancelButtonText = "No", From 712587d8622b70ec256f5cfbb833c70a6d1cf446 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:46:51 +0300 Subject: [PATCH 45/47] docs: COMMANDS.md --- docs/COMMANDS.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index b36059db..8d1a19f7 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -96,4 +96,13 @@ This set of commands allows moderators or higher-level users to manage the black - **Description**: Check if a user is blacklisted. - **Parameters**: - `user`: The user to check. -- **Usage**: `/blacklist status [user]` \ No newline at end of file +- **Usage**: `/blacklist status [user]` + +## Tag Slash Commands + +### `/tag` + +- **Description**: Get tag content. +- **Parameters**: + - `name`: Tag name. +- **Usage**: `/tag [name]` \ No newline at end of file From 3bd91ec45d2dfd7ea53c01fcc379fad7402095fa Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 23:04:37 +0300 Subject: [PATCH 46/47] chore: DSharpPlus nuget nightly upgrade --- src/Modmail.NET/Modmail.NET.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Modmail.NET/Modmail.NET.csproj b/src/Modmail.NET/Modmail.NET.csproj index 2a643dd6..59ea81ac 100644 --- a/src/Modmail.NET/Modmail.NET.csproj +++ b/src/Modmail.NET/Modmail.NET.csproj @@ -17,9 +17,9 @@ - - - + + + From 36d3edf15ec77a950169d31d67ef46eec2719277 Mon Sep 17 00:00:00 2001 From: Berkay <77205615+bberka@users.noreply.github.com> Date: Tue, 8 Apr 2025 23:06:06 +0300 Subject: [PATCH 47/47] docs: CHANGELOG.md --- docs/CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f2e60167..c85509fa 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,38 @@ # **Changelogs** +## **v2.3** +### Features + +- **Tag System Implementation:** Introduced a tag system, allowing users to trigger pre-defined responses from the bot using a 'tag' slash command. + - Implemented web UI and database infrastructure for managing tags (create, view, update, delete). + - Added command handlers for tag retrieval and execution. + - Implemented a `TagProvider` class to dynamically generate slash commands for available tags. + - Implemented a `TagBotMessages` helper class to centralize the creation of messages sent by the bot when using tags. + +- **Enhanced User Engagement with Mirrored Reactions:** Reactions added by moderators in ticket channels are now automatically mirrored to the corresponding user's direct messages, and vice versa, creating a more interactive communication experience. + +- **Implement mirrored message deletion** The bot now removes original user messages from DMs after they are removed to maintain organization and clarity of communications. + +- **Feedback implementation:** The feedback submission system is improved and a data list has been added to improve visibility of user interaction. + +- **Automatic Ticket Data Deletion and Optional Timeout**: Added support for automatically deleting timeout tickets. + +- **Add preliminary permission policies (currently inactive)** Added the configuration for authorization to be tested and applied by admins once the implementation is finished. + +- **Enhance Guild Option UI with visual separators and descriptions:** the visual clarity is improved by seperating groups of guild options through horizontal lines. + +### Bug Fixes + +- Fixed minor UI text issues +- Fixed incorrect metric calculation +- Fixed bot intents +- Fixed multiple feedback submissions +### Dependency Updates + +- Updated DSharpPlus to the latest NuGet nightly version. + + + ## **v2.2**