summary refs log tree commit diff
path: root/extra/admin-api/Spacebar.AdminAPI/Controllers/GuildController.cs
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-12-14 23:18:35 +0100
committerRory& <root@rory.gay>2025-12-14 23:18:35 +0100
commit4534d4dbf5f5071fa71c5dec4444bf4b2bbd040c (patch)
tree614304bd951f09a73155a9bd9ee192cf946e854a /extra/admin-api/Spacebar.AdminAPI/Controllers/GuildController.cs
parentNo longer generate security_jwtSecret, switch webauthn to modern tokens, dont (diff)
downloadserver-ts-4534d4dbf5f5071fa71c5dec4444bf4b2bbd040c.tar.xz
admin api packaging
Diffstat (limited to 'extra/admin-api/Spacebar.AdminAPI/Controllers/GuildController.cs')
-rw-r--r--extra/admin-api/Spacebar.AdminAPI/Controllers/GuildController.cs323
1 files changed, 0 insertions, 323 deletions
diff --git a/extra/admin-api/Spacebar.AdminAPI/Controllers/GuildController.cs b/extra/admin-api/Spacebar.AdminAPI/Controllers/GuildController.cs
deleted file mode 100644

index 03b38e64d..000000000 --- a/extra/admin-api/Spacebar.AdminAPI/Controllers/GuildController.cs +++ /dev/null
@@ -1,323 +0,0 @@ -using ArcaneLibs.Extensions; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using RabbitMQ.Client; -using Spacebar.AdminAPI.Extensions; -using Spacebar.AdminApi.Models; -using Spacebar.AdminAPI.Services; -using Spacebar.Db.Contexts; -using Spacebar.Db.Models; -using Spacebar.RabbitMqUtilities; - -namespace Spacebar.AdminAPI.Controllers; - -[ApiController] -[Route("/Guilds")] -public class GuildController(ILogger<GuildController> logger, Configuration config, RabbitMQConfiguration amqpConfig, SpacebarDbContext db, RabbitMQService mq, IServiceProvider sp, AuthenticationService auth) : ControllerBase { - private readonly ILogger<GuildController> _logger = logger; - - [HttpGet] - public async IAsyncEnumerable<GuildModel> Get() { - (await auth.GetCurrentUser(Request)).GetRights().AssertHasAllRights(SpacebarRights.Rights.OPERATOR); - - var results = db.Guilds.Select(x => new GuildModel { - Id = x.Id, - AfkChannelId = x.AfkChannelId, - AfkTimeout = x.AfkTimeout, - Banner = x.Banner, - DefaultMessageNotifications = x.DefaultMessageNotifications, - Description = x.Description, - DiscoverySplash = x.DiscoverySplash, - ExplicitContentFilter = x.ExplicitContentFilter, - Features = x.Features, - PrimaryCategoryId = x.PrimaryCategoryId, - Icon = x.Icon, - Large = x.Large, - MaxMembers = x.MaxMembers, - MaxPresences = x.MaxPresences, - MaxVideoChannelUsers = x.MaxVideoChannelUsers, - MemberCount = x.MemberCount, - PresenceCount = x.PresenceCount, - TemplateId = x.TemplateId, - MfaLevel = x.MfaLevel, - Name = x.Name, - OwnerId = x.OwnerId, - PreferredLocale = x.PreferredLocale, - PremiumSubscriptionCount = x.PremiumSubscriptionCount, - PremiumTier = x.PremiumTier, - PublicUpdatesChannelId = x.PublicUpdatesChannelId, - RulesChannelId = x.RulesChannelId, - Region = x.Region, - Splash = x.Splash, - SystemChannelId = x.SystemChannelId, - SystemChannelFlags = x.SystemChannelFlags, - Unavailable = x.Unavailable, - VerificationLevel = x.VerificationLevel, - WelcomeScreen = x.WelcomeScreen, - WidgetChannelId = x.WidgetChannelId, - WidgetEnabled = x.WidgetEnabled, - NsfwLevel = x.NsfwLevel, - Nsfw = x.Nsfw, - Parent = x.Parent, - PremiumProgressBarEnabled = x.PremiumProgressBarEnabled, - ChannelOrdering = x.ChannelOrdering, - ChannelCount = x.Channels.Count(), - RoleCount = x.Roles.Count(), - EmojiCount = x.Emojis.Count(), - StickerCount = x.Stickers.Count(), - InviteCount = x.Invites.Count(), - MessageCount = x.Messages.Count(), - BanCount = x.Bans.Count(), - VoiceStateCount = x.VoiceStates.Count(), - }).AsAsyncEnumerable(); - await foreach (var result in results) { - yield return result; - } - } - - [HttpPost("{id}/force_join")] - public async Task<IActionResult> ForceJoinGuild([FromBody] ForceJoinRequest request, string id) { - (await auth.GetCurrentUser(Request)).GetRights().AssertHasAllRights(SpacebarRights.Rights.OPERATOR); - - var guild = await db.Guilds.FindAsync(id); - if (guild == null) { - return NotFound(new { entity = "Guild", id, message = "Guild not found" }); - } - - var userId = request.UserId ?? config.OverrideUid ?? (await auth.GetCurrentUser(Request)).Id; - var user = await db.Users.FindAsync(userId); - if (user == null) { - return NotFound(new { entity = "User", id = userId, message = "User not found" }); - } - - var member = await db.Members.SingleOrDefaultAsync(m => m.GuildId == id && m.Id == userId); - if (member is null) { - member = new Member { - Id = userId, - GuildId = id, - JoinedAt = DateTime.UtcNow, - PremiumSince = 0, - Roles = [await db.Roles.SingleAsync(r => r.Id == id)], - Pending = false - }; - await db.Members.AddAsync(member); - guild.MemberCount++; - db.Guilds.Update(guild); - await db.SaveChangesAsync(); - } - - if (request.MakeOwner) { - guild.OwnerId = userId; - db.Guilds.Update(guild); - await db.SaveChangesAsync(); - } else if (request.MakeAdmin) { - var roles = await db.Roles.Where(r => r.GuildId == id).OrderBy(x=>x.Position).ToListAsync(); - var adminRole = roles.FirstOrDefault(r => r.Permissions == "8" || r.Permissions == "9"); // Administrator - if (adminRole == null) { - adminRole = new Role { - Id = Guid.NewGuid().ToString(), - GuildId = id, - Name = "Instance administrator", - Color = 0, - Hoist = false, - Position = roles.Max(x=>x.Position) + 1, - Permissions = "8", // Administrator - Managed = false, - Mentionable = false - }; - await db.Roles.AddAsync(adminRole); - await db.SaveChangesAsync(); - } - - if (!member.Roles.Any(r => r.Id == adminRole.Id)) { - member.Roles.Add(adminRole); - db.Members.Update(member); - await db.SaveChangesAsync(); - } - } - - // TODO: gateway events - - return Ok(new { entity = "Guild", id, message = "Guild join forced" }); - } - - [HttpGet("{id}/delete")] - public async IAsyncEnumerable<AsyncActionResult> DeleteUser(string id, [FromQuery] int messageDeleteChunkSize = 100) { - (await auth.GetCurrentUser(Request)).GetRights().AssertHasAllRights(SpacebarRights.Rights.OPERATOR); - - var user = await db.Users.FindAsync(id); - if (user == null) { - Console.WriteLine($"User {id} not found"); - yield return new AsyncActionResult("ERROR", new { entity = "User", id, message = "User not found" }); - yield break; - } - - user.Data = "{}"; - user.Deleted = true; - user.Disabled = true; - user.Rights = 0; - db.Users.Update(user); - await db.SaveChangesAsync(); - - var factory = new ConnectionFactory { - Uri = new Uri("amqp://guest:guest@127.0.0.1/") - }; - await using var mqConnection = await factory.CreateConnectionAsync(); - await using var mqChannel = await mqConnection.CreateChannelAsync(); - - var messages = db.Messages - .AsNoTracking() - .Where(m => m.AuthorId == id); - var channels = messages - .Select(m => new { m.ChannelId, m.GuildId }) - .Distinct() - .ToList(); - yield return new("STATS", - new { - total_messages = messages.Count(), total_channels = channels.Count, - messages_per_channel = channels.ToDictionary(c => c.ChannelId, c => messages.Count(m => m.ChannelId == c.ChannelId)) - }); - var results = channels - .Select(ctx => DeleteMessagesForChannel(ctx.GuildId, ctx.ChannelId!, id, mqChannel, messageDeleteChunkSize)) - .ToList(); - var a = AggregateAsyncEnumerablesWithoutOrder(results); - await foreach (var result in a) { - yield return result; - } - - await db.Database.ExecuteSqlRawAsync("VACUUM FULL messages"); - await db.Database.ExecuteSqlRawAsync("REINDEX TABLE messages"); - } - - private async IAsyncEnumerable<AsyncActionResult> DeleteMessagesForChannel( - // context - string? guildId, string channelId, string authorId, - // connections - IChannel mqChannel, - // options - int messageDeleteChunkSize = 100 - ) { - { - await using var ctx = sp.CreateAsyncScope(); - await using var _db = ctx.ServiceProvider.GetRequiredService<SpacebarDbContext>(); - await mqChannel.ExchangeDeclareAsync(exchange: channelId!, type: ExchangeType.Fanout, durable: false); - 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; - } - - var props = new BasicProperties() { Type = "MESSAGE_BULK_DELETE" }; - var publishSuccess = false; - do { - try { - await mqChannel.BasicPublishAsync(exchange: channelId!, routingKey: "", mandatory: true, basicProperties: props, body: new { - ids = messageIds, - channel_id = channelId, - guild_id = guildId, - }.ToJson().AsBytes().ToArray()); - publishSuccess = true; - } - catch (Exception e) { - Console.WriteLine($"[RabbitMQ] Error publishing bulk delete: {e.Message}"); - await Task.Delay(10); - } - } while (!publishSuccess); - - 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 - } - } - } - } - - // { - // "op": 0, - // "t": "GUILD_ROLE_UPDATE", - // "d": { - // "guild_id": "1006649183970562092", - // "role": { - // "id": "1006706520514028812", - // "guild_id": "1006649183970562092", - // "color": 16711680, - // "hoist": true, - // "managed": false, - // "mentionable": true, - // "name": "Adminstrator", - // "permissions": "9", - // "position": 5, - // "unicode_emoji": "💖", - // "flags": 0 - // } - // }, - // "s": 38 - // } - -} \ No newline at end of file