summary refs log tree commit diff
path: root/extra/admin-api/Spacebar.AdminApi/Controllers/ChannelController.cs
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2026-02-20 03:18:44 +0100
committerRory& <root@rory.gay>2026-02-20 03:20:33 +0100
commita06e981150359c9765b229c297671f75fc2e4704 (patch)
tree77bd7b7853a30305fd1b3c1c4e4243beb1e8a77f /extra/admin-api/Spacebar.AdminApi/Controllers/ChannelController.cs
parentC# deps (diff)
downloadserver-ts-a06e981150359c9765b229c297671f75fc2e4704.tar.xz
Admin API work
Diffstat (limited to 'extra/admin-api/Spacebar.AdminApi/Controllers/ChannelController.cs')
-rw-r--r--extra/admin-api/Spacebar.AdminApi/Controllers/ChannelController.cs134
1 files changed, 134 insertions, 0 deletions
diff --git a/extra/admin-api/Spacebar.AdminApi/Controllers/ChannelController.cs b/extra/admin-api/Spacebar.AdminApi/Controllers/ChannelController.cs
new file mode 100644

index 00000000..45a9e437 --- /dev/null +++ b/extra/admin-api/Spacebar.AdminApi/Controllers/ChannelController.cs
@@ -0,0 +1,134 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Spacebar.Interop.Replication.Abstractions; +using Spacebar.AdminApi.Extensions; +using Spacebar.Models.AdminApi; +using Spacebar.Interop.Authentication.AspNetCore; +using Spacebar.Models.Db.Contexts; +using Spacebar.Models.Db.Models; + +namespace Spacebar.AdminApi.Controllers; + +[ApiController] +[Route("/channels")] +public class ChannelController( + ILogger<ChannelController> logger, + SpacebarDbContext db, + IServiceProvider sp, + SpacebarAspNetAuthenticationService auth, + ISpacebarReplication replication +) : ControllerBase { + + [HttpDelete("{id}")] + public async Task DeleteById(string id) { + (await auth.GetCurrentUserAsync(Request)).GetRights().AssertHasAllRights(SpacebarRights.Rights.OPERATOR); + replication.SendAsync(new() { + Origin = "AdminApi/DeleteChannelById", + ChannelId = id, + Event = "CHANNEL_DELETE", + Payload = await db.Channels.SingleAsync (x=>x.Id == id) + }); + + await db.Channels.Where(x => x.Id == id).ExecuteDeleteAsync(); + } + + private async IAsyncEnumerable<AsyncActionResult> DeleteMessagesForChannel( + // context + string? guildId, string channelId, string authorId, + // options + int messageDeleteChunkSize = 100 + ) { + { + await using var ctx = sp.CreateAsyncScope(); + await using var _db = ctx.ServiceProvider.GetRequiredService<SpacebarDbContext>(); + var messagesInChannel = _db.Messages.AsNoTracking().Count(m => m.AuthorId == authorId && m.ChannelId == channelId && m.GuildId == guildId); + var remaining = messagesInChannel; + while (true) { + var messageIds = _db.Database.SqlQuery<string>($""" + DELETE FROM messages + WHERE id IN ( + SELECT id FROM messages + WHERE author_id = {authorId} + AND channel_id = {channelId} + AND guild_id = {guildId} + LIMIT {messageDeleteChunkSize} + ) RETURNING id; + """).ToList(); + if (messageIds.Count == 0) { + break; + } + + await replication.SendAsync(new() { + ChannelId = channelId, + Event = "MESSAGE_BULK_DELETE", + Payload = new { + ids = messageIds, + channel_id = channelId, + guild_id = guildId, + }, + Origin = "Admin API (GuildController.DeleteUser)", + }); + + yield return new("BULK_DELETED", new { + channel_id = channelId, + total = messagesInChannel, + deleted = messageIds.Count, + remaining = remaining -= messageIds.Count, + }); + await Task.Yield(); + } + } + } + + private async IAsyncEnumerable<T> AggregateAsyncEnumerablesWithoutOrder<T>(params IEnumerable<IAsyncEnumerable<T>> enumerables) { + var enumerators = enumerables.Select(e => e.GetAsyncEnumerator()).ToList(); + var tasks = enumerators.Select(e => e.MoveNextAsync().AsTask()).ToList(); + + try { + while (tasks.Count > 0) { + var completedTask = await Task.WhenAny(tasks); + var completedTaskIndex = tasks.IndexOf(completedTask); + + if (completedTask.IsCanceled) { + try { + await enumerators[completedTaskIndex].DisposeAsync(); + } + catch { + // ignored + } + + enumerators.RemoveAt(completedTaskIndex); + tasks.RemoveAt(completedTaskIndex); + continue; + } + + if (await completedTask) { + var enumerator = enumerators[completedTaskIndex]; + yield return enumerator.Current; + tasks[completedTaskIndex] = enumerator.MoveNextAsync().AsTask(); + } + else { + try { + await enumerators[completedTaskIndex].DisposeAsync(); + } + catch { + // ignored + } + + enumerators.RemoveAt(completedTaskIndex); + tasks.RemoveAt(completedTaskIndex); + } + } + } + finally { + foreach (var enumerator in enumerators) { + try { + await enumerator.DisposeAsync(); + } + catch { + // ignored + } + } + } + } +} \ No newline at end of file