From a22057b9fa59adb4c681ec4f9630220190050826 Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 31 Oct 2024 01:17:44 +0100 Subject: [PATCH] feat(dashboard): ignored users page --- ...s.cs => GuildsController.ChannelsRoles.cs} | 42 ------- .../Api/GuildsController.Users.cs | 106 ++++++++++++++++ Catalogger.Backend/Api/GuildsController.cs | 1 + .../Cache/InMemoryCache/UserCache.cs | 1 + .../src/routes/dash/[guildId]/+layout.svelte | 8 +- .../[guildId]/ignored-channels/+page.svelte | 5 +- .../dash/[guildId]/ignored-users/+page.svelte | 119 ++++++++++++++++++ .../dash/[guildId]/ignored-users/+page.ts | 10 ++ 8 files changed, 247 insertions(+), 45 deletions(-) rename Catalogger.Backend/Api/{GuildsController.Ignores.cs => GuildsController.ChannelsRoles.cs} (71%) create mode 100644 Catalogger.Backend/Api/GuildsController.Users.cs create mode 100644 Catalogger.Frontend/src/routes/dash/[guildId]/ignored-users/+page.svelte create mode 100644 Catalogger.Frontend/src/routes/dash/[guildId]/ignored-users/+page.ts diff --git a/Catalogger.Backend/Api/GuildsController.Ignores.cs b/Catalogger.Backend/Api/GuildsController.ChannelsRoles.cs similarity index 71% rename from Catalogger.Backend/Api/GuildsController.Ignores.cs rename to Catalogger.Backend/Api/GuildsController.ChannelsRoles.cs index f9cc106..23e424a 100644 --- a/Catalogger.Backend/Api/GuildsController.Ignores.cs +++ b/Catalogger.Backend/Api/GuildsController.ChannelsRoles.cs @@ -65,48 +65,6 @@ public partial class GuildsController return NoContent(); } - [HttpGet("users")] - public async Task ListUsersAsync(string id, [FromQuery] string query) - { - var (guildId, _) = await ParseGuildAsync(id); - var members = await memberCache.GetMemberNamesAsync(guildId, query); - - return Ok(members.OrderBy(m => m.Name).Select(m => new UserQueryResponse(m.Name, m.Id))); - } - - private record UserQueryResponse(string Name, string Id); - - [HttpPut("ignored-users/{userId}")] - public async Task AddIgnoredUserAsync(string id, ulong userId) - { - var (guildId, _) = await ParseGuildAsync(id); - var guildConfig = await guildRepository.GetAsync(guildId); - - if (guildConfig.Channels.IgnoredUsers.Contains(userId)) - return NoContent(); - - var user = await memberCache.TryGetAsync(guildId, DiscordSnowflake.New(userId)); - if (user == null) - return NoContent(); - - guildConfig.Channels.IgnoredUsers.Add(userId); - await guildRepository.UpdateChannelConfigAsync(guildId, guildConfig.Channels); - - return NoContent(); - } - - [HttpDelete("ignored-users/{userId}")] - public async Task RemoveIgnoredUserAsync(string id, ulong userId) - { - var (guildId, _) = await ParseGuildAsync(id); - var guildConfig = await guildRepository.GetAsync(guildId); - - guildConfig.Channels.IgnoredUsers.Remove(userId); - await guildRepository.UpdateChannelConfigAsync(guildId, guildConfig.Channels); - - return NoContent(); - } - [HttpPut("key-roles/{roleId}")] public async Task AddKeyRoleAsync(string id, ulong roleId) { diff --git a/Catalogger.Backend/Api/GuildsController.Users.cs b/Catalogger.Backend/Api/GuildsController.Users.cs new file mode 100644 index 0000000..f762d1e --- /dev/null +++ b/Catalogger.Backend/Api/GuildsController.Users.cs @@ -0,0 +1,106 @@ +// Copyright (C) 2021-present sam (starshines.gay) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System.Net; +using Catalogger.Backend.Api.Middleware; +using Catalogger.Backend.Extensions; +using Microsoft.AspNetCore.Mvc; +using Remora.Discord.API; +using Remora.Discord.API.Abstractions.Objects; + +namespace Catalogger.Backend.Api; + +public partial class GuildsController +{ + [HttpGet("ignored-users")] + public async Task GetIgnoredUsersAsync(string id, CancellationToken ct = default) + { + var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + // not actually sure how long fetching members might take. timing it out after 10 seconds just in case + // the underlying redis library doesn't support CancellationTokens so we don't pass it down + // we just end the loop early if it expires + cts.CancelAfter(TimeSpan.FromSeconds(10)); + + var (guildId, _) = await ParseGuildAsync(id); + var guildConfig = await guildRepository.GetAsync(guildId); + + var output = new List(); + foreach (var userId in guildConfig.Channels.IgnoredUsers) + { + if (cts.Token.IsCancellationRequested) + break; + + var member = await memberCache.TryGetAsync(guildId, DiscordSnowflake.New(userId)); + output.Add( + new IgnoredUser( + Id: userId, + Tag: member != null ? member.User.Value.Tag() : "unknown user" + ) + ); + } + + return Ok(output.OrderBy(i => i.Id)); + } + + private record IgnoredUser(ulong Id, string Tag); + + [HttpPut("ignored-users/{userId}")] + public async Task AddIgnoredUserAsync(string id, ulong userId) + { + var (guildId, _) = await ParseGuildAsync(id); + var guildConfig = await guildRepository.GetAsync(guildId); + + IUser? user; + var member = await memberCache.TryGetAsync(guildId, DiscordSnowflake.New(userId)); + if (member != null) + user = member.User.Value; + else + user = await userCache.GetUserAsync(DiscordSnowflake.New(userId)); + + if (user == null) + throw new ApiError(HttpStatusCode.NotFound, ErrorCode.BadRequest, "User not found"); + + if (guildConfig.Channels.IgnoredUsers.Contains(user.ID.Value)) + return Ok(new IgnoredUser(user.ID.Value, user.Tag())); + + guildConfig.Channels.IgnoredUsers.Add(user.ID.Value); + await guildRepository.UpdateChannelConfigAsync(guildId, guildConfig.Channels); + + return Ok(new IgnoredUser(user.ID.Value, user.Tag())); + } + + [HttpDelete("ignored-users/{userId}")] + public async Task RemoveIgnoredUserAsync(string id, ulong userId) + { + var (guildId, _) = await ParseGuildAsync(id); + var guildConfig = await guildRepository.GetAsync(guildId); + + guildConfig.Channels.IgnoredUsers.Remove(userId); + await guildRepository.UpdateChannelConfigAsync(guildId, guildConfig.Channels); + + return NoContent(); + } + + [HttpGet("users")] + public async Task ListUsersAsync(string id, [FromQuery] string query) + { + var (guildId, _) = await ParseGuildAsync(id); + var members = await memberCache.GetMemberNamesAsync(guildId, query); + + return Ok(members.OrderBy(m => m.Name).Select(m => new UserQueryResponse(m.Name, m.Id))); + } + + private record UserQueryResponse(string Name, string Id); +} diff --git a/Catalogger.Backend/Api/GuildsController.cs b/Catalogger.Backend/Api/GuildsController.cs index e1a52b0..4289bee 100644 --- a/Catalogger.Backend/Api/GuildsController.cs +++ b/Catalogger.Backend/Api/GuildsController.cs @@ -40,6 +40,7 @@ public partial class GuildsController( RoleCache roleCache, IMemberCache memberCache, IInviteCache inviteCache, + UserCache userCache, DiscordRequestService discordRequestService, IDiscordRestUserAPI userApi, WebhookExecutorService webhookExecutor diff --git a/Catalogger.Backend/Cache/InMemoryCache/UserCache.cs b/Catalogger.Backend/Cache/InMemoryCache/UserCache.cs index f99856e..d5a1313 100644 --- a/Catalogger.Backend/Cache/InMemoryCache/UserCache.cs +++ b/Catalogger.Backend/Cache/InMemoryCache/UserCache.cs @@ -13,6 +13,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +using System.Diagnostics.CodeAnalysis; using Catalogger.Backend.Extensions; using LazyCache; using Remora.Discord.API.Abstractions.Objects; diff --git a/Catalogger.Frontend/src/routes/dash/[guildId]/+layout.svelte b/Catalogger.Frontend/src/routes/dash/[guildId]/+layout.svelte index 3af8ba8..6bdb009 100644 --- a/Catalogger.Frontend/src/routes/dash/[guildId]/+layout.svelte +++ b/Catalogger.Frontend/src/routes/dash/[guildId]/+layout.svelte @@ -56,6 +56,12 @@ > Ignored channels + + Ignored users + - {#if $page.url.pathname === `/dash/${data.guild.id}` || $page.url.pathname === `/dash/${data.guild.id}/ignored-channels`} + {#if $page.url.pathname === `/dash/${data.guild.id}`} {/if} diff --git a/Catalogger.Frontend/src/routes/dash/[guildId]/ignored-channels/+page.svelte b/Catalogger.Frontend/src/routes/dash/[guildId]/ignored-channels/+page.svelte index fe0ea57..7764287 100644 --- a/Catalogger.Frontend/src/routes/dash/[guildId]/ignored-channels/+page.svelte +++ b/Catalogger.Frontend/src/routes/dash/[guildId]/ignored-channels/+page.svelte @@ -82,8 +82,9 @@

Ignored channels

- Messages from ignored channels will not be logged. Note that this does not - ignore channel update events, any changes to the channel will still be logged. + Messages from ignored channels will not be logged. Changes to ignored channels + will also not be logged, but note that ignored channels being deleted + (or new channels being created in an ignored category) will still be logged.

diff --git a/Catalogger.Frontend/src/routes/dash/[guildId]/ignored-users/+page.svelte b/Catalogger.Frontend/src/routes/dash/[guildId]/ignored-users/+page.svelte new file mode 100644 index 0000000..544d73b --- /dev/null +++ b/Catalogger.Frontend/src/routes/dash/[guildId]/ignored-users/+page.svelte @@ -0,0 +1,119 @@ + + +

Ignored users

+ +

Messages from ignored users will not be logged.

+ +
+ + + {#if toIgnore && !idRegex.test(toIgnore)} +

+ If you're not ignoring a member of your server, you need to give a + user ID, not their username. +

+ {/if} +
+ +
+ +
+ +

Currently ignored users

+ + + {#each data.users as user (user.id)} + + {user.tag} (ID: {user.id}) + + + {/each} + diff --git a/Catalogger.Frontend/src/routes/dash/[guildId]/ignored-users/+page.ts b/Catalogger.Frontend/src/routes/dash/[guildId]/ignored-users/+page.ts new file mode 100644 index 0000000..8f793f5 --- /dev/null +++ b/Catalogger.Frontend/src/routes/dash/[guildId]/ignored-users/+page.ts @@ -0,0 +1,10 @@ +import apiFetch from "$lib/api"; + +export const load = async ({ params }) => { + const users = await apiFetch>( + "GET", + `/api/guilds/${params.guildId}/ignored-users`, + ); + + return { users }; +};