Admin API work

This commit is contained in:
Rory&
2026-02-20 03:18:44 +01:00
parent ad2e2193a8
commit a06e981150
7 changed files with 258 additions and 16 deletions

View File

@@ -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
}
}
}
}
}

View File

@@ -0,0 +1,27 @@
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("/discovery")]
public class DiscoveryController(
ILogger<DiscoveryController> logger,
SpacebarDbContext db,
IServiceProvider sp,
SpacebarAspNetAuthenticationService auth,
ISpacebarReplication replication
) : ControllerBase {
[HttpGet]
public async Task GetDiscoverableGuilds() {
(await auth.GetCurrentUserAsync(Request)).GetRights().AssertHasAllRights(SpacebarRights.Rights.OPERATOR);
// var discoverableGuilds = db.Guilds
// .Where(x=>x.)
}
}

View File

@@ -98,10 +98,15 @@ public class GuildController(
member = new Member {
Id = userId,
GuildId = id,
JoinedAt = DateTime.UtcNow,
JoinedAt = DateTime.Now,
PremiumSince = 0,
Roles = [await db.Roles.SingleAsync(r => r.Id == id)],
Pending = false
Pending = false,
Settings = "{}",
Bio = "",
Mute = false,
Deaf = false,
};
await db.Members.AddAsync(member);
guild.MemberCount++;

View File

@@ -0,0 +1,54 @@
using System.Diagnostics;
using ArcaneLibs;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Spacebar.AdminApi.Extensions;
using Spacebar.Interop.Authentication;
using Spacebar.Interop.Authentication.AspNetCore;
using Spacebar.Interop.Replication.Abstractions;
using Spacebar.Models.AdminApi;
using Spacebar.Models.Db.Contexts;
namespace Spacebar.AdminApi.Controllers.TestControllers;
[ApiController]
public class EmptyDmsController(
ILogger<EmptyDmsController> logger,
SpacebarAuthenticationConfiguration config,
SpacebarDbContext db,
IServiceProvider sp,
SpacebarAspNetAuthenticationService auth,
ISpacebarReplication replication
) : ControllerBase {
[HttpGet("emptydms")]
public async IAsyncEnumerable<object> GetEmptyDms() {
(await auth.GetCurrentUserAsync(Request)).GetRights().AssertHasAllRights(SpacebarRights.Rights.OPERATOR);
// TODO channel type enum
var channels = db.Channels
.Include(x=>x.Recipients)
.Include(x=>x.Messages)
.Where(x => x.Type == 1)
.Where(x => !x.Messages.Any() && x.Recipients.Count == 1)
;
await using var db2Scope = sp.CreateAsyncScope();
await using var db3Scope = sp.CreateAsyncScope();
var db2 = db2Scope.ServiceProvider.GetRequiredService<SpacebarDbContext>();
var db3 = db3Scope.ServiceProvider.GetRequiredService<SpacebarDbContext>();
int count = 0;
await foreach (var channel in channels.AsAsyncEnumerable()) {
count++;
yield return new {
id = channel.Id,
msgs = await db2.Messages.Where(x => x.ChannelId == channel.Id).CountAsync(),
recips = await db3.Recipients.Where(x => x.ChannelId == channel.Id).CountAsync()
};
}
logger.LogInformation("Got {count} empty DM channels", count);
yield break;
}
}

View File

@@ -1,5 +1,3 @@
using System.Diagnostics;
using ArcaneLibs;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Spacebar.AdminApi.Extensions;
@@ -8,7 +6,6 @@ using Spacebar.Interop.Authentication.AspNetCore;
using Spacebar.Interop.Replication.Abstractions;
using Spacebar.Models.AdminApi;
using Spacebar.Models.Db.Contexts;
using Spacebar.Models.Db.Models;
namespace Spacebar.AdminApi.Controllers;
@@ -184,6 +181,11 @@ public class UserController(
}
}
// [HttpGet("{id}/Dms")]
// public async IEnumerable<object> GetDmsAsync(string userId) {
// yield break; // TODO
// }
private async IAsyncEnumerable<AsyncActionResult> DeleteMessagesForChannel(
// context
string? guildId, string channelId, string authorId,

View File

@@ -0,0 +1,12 @@
@Spacebar.AdminApi_HostAddress = http://localhost:5112
POST {{Spacebar.AdminApi_HostAddress}}/_spacebar/admin/guilds/1473141782615941382/force_join
Content-Type: application/json
Accept: application/json
{
"MakeOwner": true,
"UserId": "1006598230156341276"
}
###

View File

@@ -2,16 +2,17 @@
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Trace", //Warning
"Microsoft.AspNetCore.Mvc": "Warning", //Warning
"Microsoft.AspNetCore.HostFiltering": "Warning", //Warning
"Microsoft.AspNetCore.Cors": "Warning", //Warning
// "Microsoft.EntityFrameworkCore": "Warning"
"Microsoft.EntityFrameworkCore.Database.Command": "Debug"
"Microsoft.AspNetCore": "Trace",
"Microsoft.AspNetCore.Mvc": "Warning",
"Microsoft.AspNetCore.HostFiltering": "Warning",
"Microsoft.AspNetCore.Cors": "Warning",
// "Microsoft.EntityFrameworkCore": "Warning"
"Microsoft.EntityFrameworkCore.Database.Command": "Debug",
"Microsoft.AspNetCore.Server.Kestrel.Connections": "Information"
}
},
"ConnectionStrings": {
"Spacebar": "Host=127.0.0.1; Username=postgres; Database=spacebar; Port=5432; Include Error Detail=true; Maximum Pool Size=1000; Command Timeout=6000; Timeout=600;",
"Spacebar": "Host=127.0.0.1; Username=postgres; Database=spacebar; Port=5432; Include Error Detail=true; Maximum Pool Size=1000; Command Timeout=6000; Timeout=600;"
},
"RabbitMQ": {
"Host": "127.0.0.1",
@@ -19,9 +20,16 @@
"Username": "guest",
"Password": "guest"
},
"SpacebarAdminApi": {
"Enforce2FA": true,
"OverrideUid": null,
"DisableAuthentication": false
"Spacebar": {
"Authentication": {
"Enforce2FA": false,
"OverrideUid": "1006598230156341276",
"DisableAuthentication": true,
"PublicKeyPath": "../../../jwt.key.pub",
"PrivateKeyPath": "../../../jwt.key"
},
"UnixSocketReplication": {
"SocketDir": "../../.."
}
}
}