:drunk: Write shit code trying to split up the Auth (WIP)
This commit is contained in:
105
DysonNetwork.Pass/Storage/Handlers/FlushBufferService.cs
Normal file
105
DysonNetwork.Pass/Storage/Handlers/FlushBufferService.cs
Normal file
@ -0,0 +1,105 @@
|
||||
using System.Collections.Concurrent;
|
||||
using DysonNetwork.Pass.Features.Account;
|
||||
using DysonNetwork.Common.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Pass.Storage.Handlers;
|
||||
|
||||
public class FlushBufferService
|
||||
{
|
||||
private readonly ConcurrentQueue<object> _buffer = new();
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<FlushBufferService> _logger;
|
||||
private Timer? _timer;
|
||||
|
||||
public FlushBufferService(IServiceScopeFactory scopeFactory, ILogger<FlushBufferService> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_logger = logger;
|
||||
_timer = new Timer(FlushBuffer, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
public void Enqueue(object item)
|
||||
{
|
||||
_buffer.Enqueue(item);
|
||||
}
|
||||
|
||||
private async void FlushBuffer(object? state)
|
||||
{
|
||||
if (_buffer.IsEmpty) return;
|
||||
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<Data.PassDatabase>();
|
||||
|
||||
var itemsToProcess = new List<object>();
|
||||
while (_buffer.TryDequeue(out var item))
|
||||
{
|
||||
itemsToProcess.Add(item);
|
||||
}
|
||||
|
||||
if (itemsToProcess.Count == 0) return;
|
||||
|
||||
_logger.LogInformation("Flushing {Count} items from buffer.", itemsToProcess.Count);
|
||||
|
||||
foreach (var item in itemsToProcess)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case LastActiveInfo lastActiveInfo:
|
||||
await HandleLastActiveInfo(db, lastActiveInfo);
|
||||
break;
|
||||
case ActionLog actionLog:
|
||||
await HandleActionLog(db, actionLog);
|
||||
break;
|
||||
default:
|
||||
_logger.LogWarning("Unknown item type in buffer: {Type}", item.GetType().Name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error saving changes during buffer flush.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleLastActiveInfo(Data.PassDatabase db, LastActiveInfo info)
|
||||
{
|
||||
var profile = await db.AccountProfiles.FirstOrDefaultAsync(p => p.AccountId == info.Account.Id);
|
||||
if (profile != null)
|
||||
{
|
||||
profile.LastSeenAt = info.SeenAt;
|
||||
db.AccountProfiles.Update(profile);
|
||||
}
|
||||
|
||||
var session = await db.AuthSessions.FirstOrDefaultAsync(s => s.Id == info.Session.Id);
|
||||
if (session != null)
|
||||
{
|
||||
session.LastGrantedAt = info.SeenAt;
|
||||
db.AuthSessions.Update(session);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleActionLog(Data.PassDatabase db, ActionLog log)
|
||||
{
|
||||
db.ActionLogs.Add(log);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public class LastActiveInfo
|
||||
{
|
||||
public Account Account { get; set; } = null!;
|
||||
public AuthSession Session { get; set; } = null!;
|
||||
public Instant SeenAt { get; set; }
|
||||
}
|
127
DysonNetwork.Pass/Storage/ICacheService.cs
Normal file
127
DysonNetwork.Pass/Storage/ICacheService.cs
Normal file
@ -0,0 +1,127 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace DysonNetwork.Pass.Storage;
|
||||
|
||||
public interface ICacheService
|
||||
{
|
||||
Task<T?> GetAsync<T>(string key);
|
||||
Task<(bool found, T? value)> GetAsyncWithStatus<T>(string key);
|
||||
Task SetAsync<T>(string key, T value, TimeSpan? expiry = null);
|
||||
Task SetWithGroupsAsync<T>(string key, T value, string[] groups, TimeSpan? expiry = null);
|
||||
Task RemoveAsync(string key);
|
||||
Task RemoveGroupAsync(string groupName);
|
||||
Task<IAsyncDisposable?> AcquireLockAsync(string key, TimeSpan expiry, TimeSpan? wait = null);
|
||||
}
|
||||
|
||||
public class InMemoryCacheService : ICacheService
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, object> _cache = new();
|
||||
private readonly ConcurrentDictionary<string, HashSet<string>> _groups = new();
|
||||
private readonly ConcurrentDictionary<string, DateTimeOffset> _expirations = new();
|
||||
|
||||
public Task<T?> GetAsync<T>(string key)
|
||||
{
|
||||
if (_cache.TryGetValue(key, out var value) && !IsExpired(key))
|
||||
{
|
||||
return Task.FromResult((T?)value);
|
||||
}
|
||||
Remove(key);
|
||||
return Task.FromResult(default(T));
|
||||
}
|
||||
|
||||
public Task<(bool found, T? value)> GetAsyncWithStatus<T>(string key)
|
||||
{
|
||||
if (_cache.TryGetValue(key, out var value) && !IsExpired(key))
|
||||
{
|
||||
return Task.FromResult((true, (T?)value));
|
||||
}
|
||||
Remove(key);
|
||||
return Task.FromResult((false, default(T)));
|
||||
}
|
||||
|
||||
public Task SetAsync<T>(string key, T value, TimeSpan? expiry = null)
|
||||
{
|
||||
_cache[key] = value!;
|
||||
_expirations[key] = expiry.HasValue ? DateTimeOffset.UtcNow.Add(expiry.Value) : DateTimeOffset.MaxValue;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SetWithGroupsAsync<T>(string key, T value, string[] groups, TimeSpan? expiry = null)
|
||||
{
|
||||
_cache[key] = value!;
|
||||
_expirations[key] = expiry.HasValue ? DateTimeOffset.UtcNow.Add(expiry.Value) : DateTimeOffset.MaxValue;
|
||||
foreach (var group in groups)
|
||||
{
|
||||
_groups.GetOrAdd(group, _ => new HashSet<string>()).Add(key);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task RemoveAsync(string key)
|
||||
{
|
||||
Remove(key);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task RemoveGroupAsync(string groupName)
|
||||
{
|
||||
if (_groups.TryRemove(groupName, out var keysToRemove))
|
||||
{
|
||||
foreach (var key in keysToRemove)
|
||||
{
|
||||
Remove(key);
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<IAsyncDisposable?> AcquireLockAsync(string key, TimeSpan expiry, TimeSpan? wait = null)
|
||||
{
|
||||
var startTime = DateTime.UtcNow;
|
||||
while (true)
|
||||
{
|
||||
if (_cache.TryAdd(key, new object()))
|
||||
{
|
||||
_expirations[key] = DateTimeOffset.UtcNow.Add(expiry);
|
||||
return new AsyncLockReleaser(() => Remove(key));
|
||||
}
|
||||
|
||||
if (wait == null || DateTime.UtcNow - startTime > wait.Value)
|
||||
{
|
||||
return null; // Could not acquire lock within wait time
|
||||
}
|
||||
await Task.Delay(50); // Wait a bit before retrying
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsExpired(string key)
|
||||
{
|
||||
return _expirations.TryGetValue(key, out var expiration) && expiration <= DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
private void Remove(string key)
|
||||
{
|
||||
_cache.TryRemove(key, out _);
|
||||
_expirations.TryRemove(key, out _);
|
||||
foreach (var group in _groups.Values)
|
||||
{
|
||||
group.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private class AsyncLockReleaser : IAsyncDisposable
|
||||
{
|
||||
private readonly Action _releaseAction;
|
||||
|
||||
public AsyncLockReleaser(Action releaseAction)
|
||||
{
|
||||
_releaseAction = releaseAction;
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
_releaseAction();
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user