Reduce frontend polling load and clean stale UI hooks

This commit is contained in:
2026-02-08 21:57:47 +01:00
parent 726ba79fdf
commit d375b942ff
13 changed files with 447 additions and 281 deletions

View File

@@ -1,8 +1,14 @@
using System.Collections.Concurrent;
namespace GameList.Endpoints;
internal static class SuggestionValidator
{
public static async Task<string?> ValidateAsync(SuggestionInput input, IHttpClientFactory httpFactory)
private static readonly ConcurrentDictionary<string, (bool Reachable, DateTimeOffset ExpiresAt)> ImageReachabilityCache = new(StringComparer.OrdinalIgnoreCase);
private static readonly TimeSpan ReachableCacheTtl = TimeSpan.FromMinutes(15);
private static readonly TimeSpan UnreachableCacheTtl = TimeSpan.FromMinutes(2);
public static async Task<string?> ValidateAsync(SuggestionInput input, IHttpClientFactory httpFactory, bool shouldValidateImageReachability = true)
{
if (string.IsNullOrWhiteSpace(input.Name) || input.Name.Length > 100)
return "Name is required and must be <= 100 characters.";
@@ -10,7 +16,7 @@ internal static class SuggestionValidator
if (!EndpointHelpers.IsValidImageUrl(input.ScreenshotUrl))
return "Screenshot URL must be http(s) and end with an image file extension.";
if (!await EndpointHelpers.IsReachableImageAsync(input.ScreenshotUrl, httpFactory))
if (shouldValidateImageReachability && !await IsReachableImageCachedAsync(input.ScreenshotUrl, httpFactory))
return "Screenshot URL could not be validated as an image. Use a public image link (http/https, no redirects, max 5 MB).";
if (!EndpointHelpers.IsValidHttpUrl(input.GameUrl))
@@ -22,6 +28,21 @@ internal static class SuggestionValidator
return ValidatePlayers(input.MinPlayers, input.MaxPlayers);
}
private static async Task<bool> IsReachableImageCachedAsync(string? url, IHttpClientFactory httpFactory)
{
if (string.IsNullOrWhiteSpace(url))
return true;
var normalized = url.Trim();
if (ImageReachabilityCache.TryGetValue(normalized, out var cached) && cached.ExpiresAt > DateTimeOffset.UtcNow)
return cached.Reachable;
var reachable = await EndpointHelpers.IsReachableImageAsync(normalized, httpFactory);
var ttl = reachable ? ReachableCacheTtl : UnreachableCacheTtl;
ImageReachabilityCache[normalized] = (reachable, DateTimeOffset.UtcNow.Add(ttl));
return reachable;
}
private static string? ValidatePlayers(int? minPlayers, int? maxPlayers)
{
if (minPlayers is null && maxPlayers is null)