feat: Purge slugs when they are expired
This commit is contained in:
41
backend/src/Web/Features/Contents/Data/SlugPurger.cs
Normal file
41
backend/src/Web/Features/Contents/Data/SlugPurger.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
public class SlugPurger(ContentDbContext context)
|
||||
{
|
||||
private static readonly SemaphoreSlim _semaphore = new(1, 1);
|
||||
private static DateTimeOffset _lastPurgeTime = DateTimeOffset.MinValue;
|
||||
private static readonly TimeSpan _minTimeBetweenPurges = TimeSpan.FromSeconds(10);
|
||||
|
||||
public async Task PurgeExpiredSlugsAsync(CancellationToken ct)
|
||||
{
|
||||
// Try to acquire the semaphore
|
||||
if (!await _semaphore.WaitAsync(0, ct))
|
||||
{
|
||||
// Another purge operation is in progress, skip this one
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
if (now - _lastPurgeTime < _minTimeBetweenPurges)
|
||||
{
|
||||
// Not enough time has passed since the last purge
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete expired slugs that are not in use
|
||||
await context
|
||||
.Slugs
|
||||
.Where(s => s.ReservedUntil < now && s.UsedBy == null)
|
||||
.ExecuteDeleteAsync(ct);
|
||||
|
||||
// Update the last purge time regardless of whether we found expired slugs or not
|
||||
_lastPurgeTime = now;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ public static class DependencyInjection
|
||||
builder.Services.AddDbContext<ContentDbContext>(configureAction);
|
||||
builder.Services.AddScoped<ContentDbContextInitializer>();
|
||||
builder.Services.Configure<ContentOptions>(builder.Configuration.GetSection(ContentOptions.ConfigurationSection));
|
||||
builder.Services.AddScoped<SlugPurger>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ public sealed class ReserveSlugRequestValidator : Validator<ReserveSlugRequest>
|
||||
[PublicAPI]
|
||||
public sealed class ReserveSlug(
|
||||
ContentDbContext context,
|
||||
IOptions<ContentOptions> opts)
|
||||
IOptions<ContentOptions> opts,
|
||||
SlugPurger slugPurger)
|
||||
: Endpoint<ReserveSlugRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
@@ -46,6 +47,9 @@ public sealed class ReserveSlug(
|
||||
|
||||
try
|
||||
{
|
||||
// First, purge any expired slugs
|
||||
await slugPurger.PurgeExpiredSlugsAsync(ct);
|
||||
|
||||
var reservation = await context.Slugs.FirstOrDefaultAsync(
|
||||
s => s.Id == req.ReservationId && s.CreatedBy == User.GetUserId(),
|
||||
cancellationToken: ct);
|
||||
|
||||
Reference in New Issue
Block a user