Newer
Older
percord / extra / admin-api / Spacebar.GatewayOffload / Controllers / Op12Controller.cs
using System.Linq.Expressions;
using System.Text.Json;
using System.Text.Json.Nodes;
using ArcaneLibs.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Spacebar.DataMappings.Generic;
using Spacebar.Interop.Authentication.AspNetCore;
using Spacebar.Interop.Replication.Abstractions;
using Spacebar.Models.Db.Contexts;
using Spacebar.Models.Db.Models;
using Spacebar.Models.Gateway;

namespace Spacebar.GatewayOffload.Controllers;

[ApiController]
[Route("/_spacebar/offload/gateway/GuildSync")]
public class Op12Controller(ILogger<Op12Controller> logger, SpacebarAspNetAuthenticationService authService, SpacebarDbContext db, IServiceProvider sp) : ControllerBase
{
    [HttpPost("")]
    public async IAsyncEnumerable<ReplicationMessage> DoGuildSync(List<string> guildIds)
    {
        var user = await authService.GetCurrentUserAsync(Request);
        guildIds = (await db.Members.Where(x => x.Id == user.Id).Select(x => x.GuildId).ToListAsync())
            .Intersect(guildIds)
            .OrderByDescending(gi => db.Members.Count(m => m.GuildId == gi))
            .ToList();

        var syncs = guildIds.Select(GetGuildSyncAsync).ToList().ToAsyncResultEnumerable();
        await foreach (var res in syncs)
        {
            yield return new()
            {
                Origin = "OFFLOAD_GUILD_SYNC",
                UserId = user.Id,
                Event = "GUILD_SYNC",
                CreatedAt = DateTime.Now,
                Payload = res
            };
        }
    }

    // TODO: figure out how to abstract this to a function without EFCore complaining about not being translatable...
    //private static Func<Session, bool>  IsOnline = (Session session) => session.Status != "offline" && session.Status != "invisible" && session.Status != "unknown";

    private async Task<GuildSyncResponse> GetGuildSyncAsync(string guildId)
    {
        await using var sc = sp.CreateAsyncScope();
        var _db = sc.ServiceProvider.GetRequiredService<SpacebarDbContext>();
        var memberCount = await _db.Members.Where(x => x.GuildId == guildId).CountAsync();

        var offlineTreshold = DateTime.Now.Subtract(TimeSpan.FromDays(14));
        var isLargeGuild = memberCount > 10000;

        var members = await _db.Members.Where(x => x.GuildId == guildId)
            .Include(x => x.IdNavigation)
            .ThenInclude(x => x.Sessions.Where(s =>
                !s.IsAdminSession && (
                    // see TODO on IsOnline
                    s.Status != "offline" && s.Status != "invisible" && s.Status != "unknown"
                ) && (!isLargeGuild || s.LastSeen >= offlineTreshold)))
            .Where(x => x.IdNavigation.Sessions.Count > 0) // ignore members without sessions
            .ToListAsync();

        var mappedPartialUsers = members.Select(x => x.IdNavigation).ToDictionary(x => x.Id, x => x.ToPartialUser());
        var mappedMembers = members.ToDictionary(m => m.Id, m => m.ToPublicMember(mappedPartialUsers[m.Id]));

        var presences = members.Select(x => x.IdNavigation).Where(x => x.Sessions.Count > 0).ToDictionary(x => x.Id, x =>
        {
            var sortedSessions = x.Sessions.OrderByDescending(s => s.LastSeen).ToList();
            return new PresenceResponse()
            {
                GuildId = guildId,
                User = mappedPartialUsers[x.Id],
                Activities = x.Sessions.Where(s => s.Status is not ("offline" or "invisible" or "unknown"))
                    .SelectMany(s => JsonSerializer.Deserialize<JsonObject[]>(s.Activities) ?? []).ToList(),
                Status = sortedSessions.FirstOrDefault(s => !string.IsNullOrWhiteSpace(s.Status))?.Status ?? "offline",
                ClientStatus = JsonSerializer.Deserialize<PresenceResponse.ClientStatuses>(sortedSessions.First(s => !string.IsNullOrWhiteSpace(s.ClientStatus)).ClientStatus) ??
                               new()
            };
        }).Where(x => x.Value.Activities.Count > 0).ToDictionary();

        var r = new GuildSyncResponse()
        {
            GuildId = guildId,
            Members = mappedMembers.Values.ToList(),
            Presences = presences.Values.ToList()
        };
        return r;
    }
}