using DorkNet.Server.Data; using DorkNet.Server.Data.Entities; using Microsoft.EntityFrameworkCore; namespace DorkNet.Server.Services; /// /// Effective-friend resolution that honors the /// toggle. /// /// When the toggle is ON, every account on the server is treated as a /// friend of every other account WITHOUT writing any relationship rows — /// the friend graph is synthesized at read time. This keeps a small private /// server frictionless (no searching + manual friend requests) and reverts /// instantly when the toggle flips off, since nothing was persisted. /// /// Blocked relationships still suppress the pairing, and the system/coach /// account () is never offered as a friend. /// /// Live propagation: flipping the toggle broadcasts /// PushNotificationId.RelationshipsInvalid, which the 2020 watch /// handles by calling Relationships.RefreshList — a full re-fetch of /// api/relationships/v2/get — so connected players see the change /// without relogging (verified against the readable March decompile: /// RecNet/Relationships.txt → OnRelationshipsInvalid → RefreshList). /// public static class RelationshipQueries { /// Coach/system account seeded at startup (Player.Id = 1). /// Excluded from the global-friends set so it never shows up as a /// friend in anyone's list. public const long SystemAccountId = 1; /// True when is still an /// auto-generated placeholder name (Player_ followed by digits /// only — see PlayerService.GenerateUsername). These are accounts /// that never set a real username; the global-friends toggle hides them /// from the synthesized "everyone" list so they don't clutter it. A /// genuine Friend row still surfaces them — only the synthetic pairing is /// suppressed. public static bool IsPlaceholderUsername(string? username) { const string prefix = "Player_"; if (string.IsNullOrEmpty(username) || !username.StartsWith(prefix, StringComparison.Ordinal)) return false; var suffix = username.AsSpan(prefix.Length); if (suffix.IsEmpty) return false; foreach (var c in suffix) if (!char.IsAsciiDigit(c)) return false; return true; } /// The effective friend ids for . With the /// global-friends toggle off this is the real Friend graph; with it on /// it's every other (non-system, non-blocked) account, excluding /// placeholder Player_NNN accounts that aren't already real /// friends. public static async Task> EffectiveFriendIdsAsync( DorkNetDbContext db, ServerSettingsService settings, long me) { if (!await settings.IsGlobalFriendsEnabledAsync()) { return await db.Relationships .Where(r => r.Status == RelationshipStatus.Friend && (r.RequesterId == me || r.TargetId == me)) .Select(r => r.RequesterId == me ? r.TargetId : r.RequesterId) .ToListAsync(); } // Global friends: everyone except myself, the system account, and // anyone in a Blocked relationship with me (either direction). var blocked = await db.Relationships .Where(r => r.Status == RelationshipStatus.Blocked && (r.RequesterId == me || r.TargetId == me)) .Select(r => r.RequesterId == me ? r.TargetId : r.RequesterId) .ToListAsync(); var realFriendSet = (await db.Relationships .Where(r => r.Status == RelationshipStatus.Friend && (r.RequesterId == me || r.TargetId == me)) .Select(r => r.RequesterId == me ? r.TargetId : r.RequesterId) .ToListAsync()).ToHashSet(); var candidates = await db.Players .Where(p => p.Id != me && p.Id != SystemAccountId && !blocked.Contains(p.Id)) .Select(p => new { p.Id, p.Username }) .ToListAsync(); // Suppress placeholder accounts unless they're an actual friend. return candidates .Where(p => realFriendSet.Contains(p.Id) || !IsPlaceholderUsername(p.Username)) .Select(p => p.Id) .ToList(); } }