using Microsoft.Extensions.Logging; namespace Nuuru.Server.Services; public interface IPostCacheInvalidationService { Task PurgePostMutationAsync( int postId, CancellationToken cancellationToken = default); Task PurgePostMutationsAsync( IEnumerable postIds, CancellationToken cancellationToken = default); } public sealed class PostCacheInvalidationService : IPostCacheInvalidationService { private readonly ICloudflareCachePurgeService _cloudflareCachePurgeService; private readonly ILogger _logger; public PostCacheInvalidationService( ICloudflareCachePurgeService cloudflareCachePurgeService, ILogger logger) { _cloudflareCachePurgeService = cloudflareCachePurgeService; _logger = logger; } public Task PurgePostMutationAsync( int postId, CancellationToken cancellationToken = default) { return PurgePostMutationsAsync([postId], cancellationToken); } public async Task PurgePostMutationsAsync( IEnumerable postIds, CancellationToken cancellationToken = default) { var paths = BuildPaths(postIds); if (paths.Count == 0) { return; } var purged = await _cloudflareCachePurgeService.PurgeUrisAsync(paths, cancellationToken); if (!purged) { _logger.LogDebug("Cloudflare post cache purge skipped or failed for {Paths}", paths); } } private static List BuildPaths(IEnumerable postIds) { return postIds .Distinct() .SelectMany(static postId => new[] { $"/api/booru/posts/{postId}/file", $"/api/booru/posts/{postId}/file-watermark", $"/api/booru/posts/{postId}/thumbnail" }) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); } }