feat(dashboard): working ignored channels page
This commit is contained in:
parent
1c43beb82f
commit
bccf7caf34
13 changed files with 304 additions and 8 deletions
112
Catalogger.Backend/Api/GuildsController.Ignores.cs
Normal file
112
Catalogger.Backend/Api/GuildsController.Ignores.cs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
using Catalogger.Backend.Database.Queries;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Remora.Discord.API;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
|
||||
namespace Catalogger.Backend.Api;
|
||||
|
||||
public partial class GuildsController
|
||||
{
|
||||
[HttpPut("ignored-channels/{channelId}")]
|
||||
public async Task<IActionResult> AddIgnoredChannelAsync(string id, ulong channelId)
|
||||
{
|
||||
var (guildId, _) = await ParseGuildAsync(id);
|
||||
var guildConfig = await db.GetGuildAsync(guildId);
|
||||
|
||||
if (guildConfig.Channels.IgnoredChannels.Contains(channelId))
|
||||
return NoContent();
|
||||
|
||||
var channel = channelCache
|
||||
.GuildChannels(guildId)
|
||||
.FirstOrDefault(c =>
|
||||
c.ID.Value == channelId
|
||||
&& c.Type
|
||||
is ChannelType.GuildText
|
||||
or ChannelType.GuildCategory
|
||||
or ChannelType.GuildAnnouncement
|
||||
or ChannelType.GuildForum
|
||||
or ChannelType.GuildMedia
|
||||
or ChannelType.GuildVoice
|
||||
);
|
||||
if (channel == null)
|
||||
return NoContent();
|
||||
|
||||
guildConfig.Channels.IgnoredChannels.Add(channelId);
|
||||
db.Update(guildConfig);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("ignored-channels/{channelId}")]
|
||||
public async Task<IActionResult> RemoveIgnoredChannelAsync(string id, ulong channelId)
|
||||
{
|
||||
var (guildId, _) = await ParseGuildAsync(id);
|
||||
var guildConfig = await db.GetGuildAsync(guildId);
|
||||
|
||||
guildConfig.Channels.IgnoredChannels.Remove(channelId);
|
||||
db.Update(guildConfig);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("users")]
|
||||
public async Task<IActionResult> 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<IActionResult> AddIgnoredUserAsync(string id, ulong userId)
|
||||
{
|
||||
var (guildId, _) = await ParseGuildAsync(id);
|
||||
var guildConfig = await db.GetGuildAsync(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);
|
||||
db.Update(guildConfig);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("ignored-users/{userId}")]
|
||||
public async Task<IActionResult> RemoveIgnoredUserAsync(string id, ulong userId)
|
||||
{
|
||||
var (guildId, _) = await ParseGuildAsync(id);
|
||||
var guildConfig = await db.GetGuildAsync(guildId);
|
||||
|
||||
guildConfig.Channels.IgnoredUsers.Remove(userId);
|
||||
db.Update(guildConfig);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
using System.Net;
|
||||
using Catalogger.Backend.Api.Middleware;
|
||||
using Catalogger.Backend.Cache;
|
||||
using Catalogger.Backend.Cache.InMemoryCache;
|
||||
using Catalogger.Backend.Database;
|
||||
using Catalogger.Backend.Database.Queries;
|
||||
|
|
@ -32,6 +33,7 @@ public partial class GuildsController(
|
|||
DatabaseContext db,
|
||||
ChannelCache channelCache,
|
||||
RedisService redisService,
|
||||
IMemberCache memberCache,
|
||||
DiscordRequestService discordRequestService
|
||||
) : ApiControllerBase
|
||||
{
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ public class GuildMembersChunkResponder(ILogger logger, IMemberCache memberCache
|
|||
);
|
||||
|
||||
await memberCache.SetManyAsync(evt.GuildID, evt.Members);
|
||||
await memberCache.SetMemberNamesAsync(evt.GuildID, evt.Members);
|
||||
|
||||
if (evt.ChunkIndex == evt.ChunkCount - 1)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ public class GuildMemberAddResponder(
|
|||
public async Task<Result> RespondAsync(IGuildMemberAdd member, CancellationToken ct = default)
|
||||
{
|
||||
await memberCache.SetAsync(member.GuildID, member);
|
||||
await memberCache.SetMemberNamesAsync(member.GuildID, [member]);
|
||||
|
||||
var user = member.User.GetOrThrow();
|
||||
userCache.UpdateUser(user);
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ public class GuildMemberRemoveResponder(
|
|||
finally
|
||||
{
|
||||
await memberCache.RemoveAsync(evt.GuildID, evt.User.ID);
|
||||
await memberCache.TryRemoveMemberNameAsync(evt.GuildID, evt.User.Username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ public class GuildMemberUpdateResponder(
|
|||
finally
|
||||
{
|
||||
await memberCache.UpdateAsync(newMember);
|
||||
|
||||
userCache.UpdateUser(newMember.User);
|
||||
}
|
||||
|
||||
|
|
@ -194,6 +195,13 @@ public class GuildMemberUpdateResponder(
|
|||
**After:** {newMember.User.Tag()}
|
||||
"""
|
||||
);
|
||||
|
||||
await memberCache.UpdateMemberNameAsync(
|
||||
newMember.GuildID,
|
||||
newMember.User.ID,
|
||||
oldUser.Tag(),
|
||||
newMember.User.Tag()
|
||||
);
|
||||
}
|
||||
|
||||
var guildConfig = await db.GetGuildAsync(newMember.GuildID, ct);
|
||||
|
|
|
|||
|
|
@ -29,4 +29,21 @@ public interface IMemberCache
|
|||
public Task MarkAsCachedAsync(Snowflake guildId);
|
||||
public Task MarkAsUncachedAsync(Snowflake guildId);
|
||||
public Task UpdateAsync(IGuildMemberUpdate newMember);
|
||||
|
||||
// These methods can be stubbed out for any implementation that isn't intended for use with the dashboard.
|
||||
public Task SetMemberNamesAsync(Snowflake guildId, IEnumerable<IGuildMember> members);
|
||||
|
||||
public Task UpdateMemberNameAsync(
|
||||
Snowflake guildId,
|
||||
Snowflake userId,
|
||||
string prevName,
|
||||
string newName
|
||||
);
|
||||
|
||||
public Task<IEnumerable<(string Name, string Id)>> GetMemberNamesAsync(
|
||||
Snowflake guildId,
|
||||
string prefix
|
||||
);
|
||||
|
||||
public Task TryRemoveMemberNameAsync(Snowflake guildId, string username);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,4 +128,21 @@ public class InMemoryMemberCache(IDiscordRestGuildAPI guildApi, ILogger logger)
|
|||
|
||||
_members[(newMember.GuildID, newMember.User.ID)] = member;
|
||||
}
|
||||
|
||||
public Task SetMemberNamesAsync(Snowflake guildId, IEnumerable<IGuildMember> members) =>
|
||||
Task.CompletedTask;
|
||||
|
||||
public Task UpdateMemberNameAsync(
|
||||
Snowflake guildId,
|
||||
Snowflake userId,
|
||||
string prevName,
|
||||
string newName
|
||||
) => Task.CompletedTask;
|
||||
|
||||
public Task<IEnumerable<(string Name, string Id)>> GetMemberNamesAsync(
|
||||
Snowflake guildId,
|
||||
string prefix
|
||||
) => Task.FromResult<IEnumerable<(string, string)>>([]);
|
||||
|
||||
public Task TryRemoveMemberNameAsync(Snowflake guildId, string username) => Task.CompletedTask;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using Catalogger.Backend.Database.Redis;
|
||||
using Catalogger.Backend.Extensions;
|
||||
using Remora.Discord.API;
|
||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
using Remora.Discord.API.Abstractions.Rest;
|
||||
using Remora.Discord.API.Objects;
|
||||
using Remora.Rest.Core;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Catalogger.Backend.Cache.RedisCache;
|
||||
|
||||
|
|
@ -146,9 +148,55 @@ public class RedisMemberCache(
|
|||
await SetInnerAsync(newMember.GuildID, member);
|
||||
}
|
||||
|
||||
public async Task SetMemberNamesAsync(Snowflake guildId, IEnumerable<IGuildMember> members)
|
||||
{
|
||||
await redisService
|
||||
.GetDatabase()
|
||||
.HashSetAsync(
|
||||
MemberNamesKey(guildId),
|
||||
members
|
||||
.Select(m => new HashEntry(m.User.Value.Tag(), m.User.Value.ID.ToString()))
|
||||
.ToArray()
|
||||
);
|
||||
}
|
||||
|
||||
public async Task UpdateMemberNameAsync(
|
||||
Snowflake guildId,
|
||||
Snowflake userId,
|
||||
string prevName,
|
||||
string newName
|
||||
) =>
|
||||
await Task.WhenAll(
|
||||
redisService.GetDatabase().HashDeleteAsync(MemberNamesKey(guildId), prevName),
|
||||
redisService
|
||||
.GetDatabase()
|
||||
.HashSetAsync(MemberNamesKey(guildId), newName, userId.ToString())
|
||||
);
|
||||
|
||||
public async Task<IEnumerable<(string Name, string Id)>> GetMemberNamesAsync(
|
||||
Snowflake guildId,
|
||||
string prefix
|
||||
)
|
||||
{
|
||||
var entries = redisService
|
||||
.GetDatabase()
|
||||
.HashScanAsync(MemberNamesKey(guildId), $"{prefix}*", 50);
|
||||
|
||||
var names = new List<(string Name, string Id)>();
|
||||
await foreach (var entry in entries)
|
||||
names.Add((entry.Name, entry.Value)!);
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
public async Task TryRemoveMemberNameAsync(Snowflake guildId, string username) =>
|
||||
await redisService.GetDatabase().HashDeleteAsync(MemberNamesKey(guildId), username);
|
||||
|
||||
private const string GuildCacheKey = "cached-guilds";
|
||||
|
||||
private static string GuildMembersKey(Snowflake guildId) => $"guild-members:{guildId}";
|
||||
|
||||
private static string MemberNamesKey(Snowflake guildId) => $"guild-member-names:{guildId}";
|
||||
}
|
||||
|
||||
internal record RedisMember(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue