using System.Text.Json; using Backend.Domain; namespace Backend.Services; public sealed record RiskRuleSetting( string Key, string Label, bool Enabled, int Threshold, int WindowMinutes, string Severity, string Description); public static class RiskRuleSettings { private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web); public static RiskRuleSetting[] Defaults { get; } = [ new("resubmitted_ballot", "Ballot erneut gespeichert", true, 1, 360, "low", "Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert."), new("rapid_vote_updates", "Voting-Burst", true, 3, 10, "high", "Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest."), new("resubmitted_nomination", "Nominierung erneut eingereicht", true, 1, 360, "low", "Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert."), new("rapid_nomination_burst", "Nominierungs-Burst", true, 10, 10, "high", "Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet."), new("duplicate_clip_submission", "Doppelter Clip", true, 1, 360, "medium", "Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht."), new("rapid_clip_burst", "Clip-Burst", true, 5, 10, "high", "Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet."), new("rapid_login_ip", "Login-Burst pro IP", true, 3, 15, "medium", "Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen."), new("rapid_demo_login_ip", "Demo-Login-Burst pro IP", true, 3, 15, "medium", "Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen."), ]; public static RiskRuleSetting[] Read(SiteSettings? settings) { var storedRules = Parse(settings?.RiskRulesJson); return Defaults .Select(defaultRule => { var storedRule = storedRules.FirstOrDefault(item => string.Equals(item.Key, defaultRule.Key, StringComparison.OrdinalIgnoreCase)); return storedRule is null ? defaultRule : Normalize(storedRule, defaultRule); }) .ToArray(); } public static string Serialize(IEnumerable rules) => JsonSerializer.Serialize(rules.Select(rule => Normalize(rule, Defaults.FirstOrDefault(item => item.Key == rule.Key) ?? rule)), JsonOptions); public static RiskRuleSetting Find(IEnumerable rules, string key) => rules.FirstOrDefault(item => string.Equals(item.Key, key, StringComparison.OrdinalIgnoreCase)) ?? Defaults.First(item => item.Key == key); private static RiskRuleSetting[] Parse(string? json) { if (string.IsNullOrWhiteSpace(json)) { return []; } try { return JsonSerializer.Deserialize(json, JsonOptions) ?? []; } catch (JsonException) { return []; } } private static RiskRuleSetting Normalize(RiskRuleSetting rule, RiskRuleSetting fallback) { var severity = rule.Severity.Trim().ToLowerInvariant() switch { "high" => "high", "medium" => "medium", "low" => "low", _ => fallback.Severity, }; return rule with { Key = fallback.Key, Label = string.IsNullOrWhiteSpace(rule.Label) ? fallback.Label : rule.Label.Trim(), Threshold = Math.Clamp(rule.Threshold, 1, 500), WindowMinutes = Math.Clamp(rule.WindowMinutes, 1, 1440), Severity = severity, Description = string.IsNullOrWhiteSpace(rule.Description) ? fallback.Description : rule.Description.Trim(), }; } }