66 lines
2.7 KiB
C#
66 lines
2.7 KiB
C#
using System.Collections.Concurrent;
|
|
|
|
namespace GameList.Endpoints;
|
|
|
|
internal static class SuggestionValidator
|
|
{
|
|
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.";
|
|
|
|
if (!EndpointHelpers.IsValidImageUrl(input.ScreenshotUrl))
|
|
return "Screenshot URL must be http(s) and end with an image file extension.";
|
|
|
|
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))
|
|
return "Game URL must be http or https.";
|
|
|
|
if (!EndpointHelpers.IsValidHttpUrl(input.YoutubeUrl))
|
|
return "YouTube URL must be http or https.";
|
|
|
|
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)
|
|
return null;
|
|
|
|
if (minPlayers is < 1 or > 32)
|
|
return "Min players must be between 1 and 32.";
|
|
|
|
if (maxPlayers is < 1 or > 32)
|
|
return "Max players must be between 1 and 32.";
|
|
|
|
if (minPlayers is null || maxPlayers is null)
|
|
return "Provide both min and max players.";
|
|
|
|
if (minPlayers > maxPlayers)
|
|
return "Min players cannot exceed max players.";
|
|
|
|
return null;
|
|
}
|
|
}
|