sprints enhancements
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Provcache.Entities;
|
||||
|
||||
namespace StellaOps.Provcache;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory implementation of the revocation ledger for testing and non-persistent scenarios.
|
||||
/// For production use, inject a PostgreSQL-backed implementation from StellaOps.Provcache.Postgres.
|
||||
/// </summary>
|
||||
public sealed class InMemoryRevocationLedger : IRevocationLedger
|
||||
{
|
||||
private readonly ConcurrentDictionary<long, RevocationEntry> _entries = new();
|
||||
private readonly ILogger<InMemoryRevocationLedger> _logger;
|
||||
private long _currentSeqNo;
|
||||
|
||||
public InMemoryRevocationLedger(ILogger<InMemoryRevocationLedger> logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<RevocationEntry> RecordAsync(
|
||||
RevocationEntry entry,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entry);
|
||||
|
||||
var seqNo = Interlocked.Increment(ref _currentSeqNo);
|
||||
var recordedEntry = entry with { SeqNo = seqNo };
|
||||
|
||||
_entries[seqNo] = recordedEntry;
|
||||
|
||||
_logger.LogInformation(
|
||||
"Recorded revocation {RevocationId} of type {Type} for key {Key}, invalidated {Count} entries",
|
||||
entry.RevocationId, entry.RevocationType, entry.RevokedKey, entry.EntriesInvalidated);
|
||||
|
||||
return Task.FromResult(recordedEntry);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<RevocationEntry>> GetEntriesSinceAsync(
|
||||
long sinceSeqNo,
|
||||
int limit = 1000,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var entries = _entries.Values
|
||||
.Where(e => e.SeqNo > sinceSeqNo)
|
||||
.OrderBy(e => e.SeqNo)
|
||||
.Take(limit)
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult<IReadOnlyList<RevocationEntry>>(entries);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<RevocationEntry>> GetEntriesByTypeAsync(
|
||||
string revocationType,
|
||||
DateTimeOffset? since = null,
|
||||
int limit = 1000,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(revocationType);
|
||||
|
||||
var query = _entries.Values
|
||||
.Where(e => e.RevocationType == revocationType);
|
||||
|
||||
if (since.HasValue)
|
||||
{
|
||||
query = query.Where(e => e.RevokedAt > since.Value);
|
||||
}
|
||||
|
||||
var entries = query
|
||||
.OrderBy(e => e.SeqNo)
|
||||
.Take(limit)
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult<IReadOnlyList<RevocationEntry>>(entries);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<long> GetLatestSeqNoAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(Interlocked.Read(ref _currentSeqNo));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<RevocationEntry>> GetRevocationsForKeyAsync(
|
||||
string revokedKey,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(revokedKey);
|
||||
|
||||
var entries = _entries.Values
|
||||
.Where(e => e.RevokedKey == revokedKey)
|
||||
.OrderBy(e => e.SeqNo)
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult<IReadOnlyList<RevocationEntry>>(entries);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<RevocationLedgerStats> GetStatsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var allEntries = _entries.Values.ToList();
|
||||
var totalEntries = allEntries.Count;
|
||||
var latestSeqNo = Interlocked.Read(ref _currentSeqNo);
|
||||
var totalInvalidated = allEntries.Sum(e => (long)e.EntriesInvalidated);
|
||||
|
||||
var entriesByType = allEntries
|
||||
.GroupBy(e => e.RevocationType)
|
||||
.ToDictionary(g => g.Key, g => (long)g.Count());
|
||||
|
||||
var oldestEntry = allEntries.MinBy(e => e.SeqNo)?.RevokedAt;
|
||||
var newestEntry = allEntries.MaxBy(e => e.SeqNo)?.RevokedAt;
|
||||
|
||||
return Task.FromResult(new RevocationLedgerStats
|
||||
{
|
||||
TotalEntries = totalEntries,
|
||||
LatestSeqNo = latestSeqNo,
|
||||
EntriesByType = entriesByType,
|
||||
TotalEntriesInvalidated = totalInvalidated,
|
||||
OldestEntryAt = oldestEntry,
|
||||
NewestEntryAt = newestEntry
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all entries (for testing).
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_entries.Clear();
|
||||
Interlocked.Exchange(ref _currentSeqNo, 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user