using Microsoft.EntityFrameworkCore; using Tiku.Application.Security; using Tiku.Infrastructure.Persistence; namespace Tiku.Infrastructure.Security; internal sealed class AuthorizationCacheInvalidationProcessor( TikuDbContext dbContext, IAccessSecurityCache cache) : IAuthorizationCacheInvalidationProcessor { public async Task ProcessPendingAsync(int batchSize = 100, CancellationToken cancellationToken = default) { if (!cache.IsConfigured) { return 0; } var items = await dbContext.AuthorizationCacheInvalidations .Where(item => item.ProcessedAt == null) .OrderBy(item => item.CreatedAt) .Take(Math.Clamp(batchSize, 1, 500)) .ToArrayAsync(cancellationToken); var processed = 0; foreach (var item in items) { try { switch (item.TargetType) { case "session" when item.SessionId.HasValue: await cache.InvalidateSessionAsync(item.SessionId.Value, cancellationToken); break; case "user" when item.UserId.HasValue: await cache.InvalidateUserAsync(item.UserId.Value, cancellationToken); break; case "tenant" when item.TenantId.HasValue: await cache.InvalidateTenantAsync(item.TenantId.Value, cancellationToken); break; case "membership" when item.TenantId.HasValue && item.UserId.HasValue: await cache.InvalidateMembershipAsync(item.TenantId.Value, item.UserId.Value, cancellationToken); break; case "scope" when item.Realm.HasValue && item.Version.HasValue: await cache.SetAuthorizationVersionAsync(item.Realm.Value, item.TenantId, item.Version.Value, cancellationToken); break; default: throw new InvalidOperationException($"Invalid authorization cache invalidation {item.Id}."); } item.ProcessedAt = DateTimeOffset.UtcNow; item.LastError = null; item.AttemptCount++; processed++; } catch (Exception exception) when (exception is not OperationCanceledException) { item.AttemptCount++; item.LastError = exception.Message; } } await dbContext.SaveChangesAsync(cancellationToken); return processed; } }