feat: Purge slugs when they are expired

This commit is contained in:
2025-04-24 01:20:59 -04:00
parent 0aa299efbb
commit a8cd970f3c
3 changed files with 47 additions and 1 deletions

View 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();
}
}
}

View File

@@ -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;
}

View File

@@ -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);