using DysonNetwork.Shared.Models; using NodaTime; using Quartz; using DysonNetwork.Shared.Services; namespace DysonNetwork.Sphere.Storage.Handlers; public class LastActiveInfo { public Session Session { get; set; } = null!; public Account Account { get; set; } = null!; public Instant SeenAt { get; set; } } public class LastActiveFlushHandler(IAccountService accounts) : IFlushHandler { public async Task FlushAsync(IReadOnlyList items) { // Remove duplicates by grouping on (sessionId, accountId), taking the most recent SeenAt var distinctItems = items .GroupBy(x => (SessionId: x.Session.Id, AccountId: x.Account.Id)) .Select(g => g.OrderByDescending(x => x.SeenAt).First()) .ToList(); // Build dictionaries so we can match session/account IDs to their new "last seen" timestamps var sessionIdMap = distinctItems .GroupBy(x => x.Session.Id) .ToDictionary(g => g.Key, g => g.Last().SeenAt); var accountIdMap = distinctItems .GroupBy(x => x.Account.Id) .ToDictionary(g => g.Key, g => g.Last().SeenAt); // Update sessions using native EF Core ExecuteUpdateAsync foreach (var kvp in sessionIdMap) await accounts.UpdateSessionLastGrantedAt(kvp.Key, kvp.Value); // Update account profiles using native EF Core ExecuteUpdateAsync foreach (var kvp in accountIdMap) await accounts.UpdateAccountProfileLastSeenAt(kvp.Key, kvp.Value); } } public class LastActiveFlushJob(FlushBufferService fbs, ActionLogFlushHandler hdl) : IJob { public async Task Execute(IJobExecutionContext context) { await fbs.FlushAsync(hdl); } }