mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
Implement "Everyone is friends" toggle: add global friends functionality with server settings and UI integration
This commit is contained in:
@@ -24,6 +24,7 @@ public class MessagesController(
|
||||
PrivateInstanceService privateInstances,
|
||||
OnlinePresenceService onlinePresence,
|
||||
PlayerPresenceService presence,
|
||||
ServerSettingsService serverSettings,
|
||||
ILogger<MessagesController> logger) : ControllerBase
|
||||
{
|
||||
private long Me => this.RequireCurrentPlayerId();
|
||||
@@ -534,11 +535,8 @@ public class MessagesController(
|
||||
[HttpGet("/api/messages/v1/onlineStatus")]
|
||||
public async Task<ActionResult> FavoriteFriendOnlineStatus()
|
||||
{
|
||||
var friendIds = 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();
|
||||
// Honors the global-friends toggle (everyone is a friend when on).
|
||||
var friendIds = await RelationshipQueries.EffectiveFriendIdsAsync(db, serverSettings, Me);
|
||||
var online = onlinePresence.OnlinePlayerIds().ToHashSet();
|
||||
return Ok(friendIds.Select(id => new
|
||||
{
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace DorkNet.Server.Controllers.API.Relationships.V2;
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class RelationshipsController(DorkNetDbContext db, NotificationService notifications) : ControllerBase
|
||||
public class RelationshipsController(DorkNetDbContext db, NotificationService notifications, ServerSettingsService settings) : ControllerBase
|
||||
{
|
||||
// RelationshipType — int values match Relationship.RelationshipType
|
||||
// in the patched dump.cs.
|
||||
@@ -72,6 +72,13 @@ public class RelationshipsController(DorkNetDbContext db, NotificationService no
|
||||
public async Task<ActionResult> GetMyRelationships()
|
||||
{
|
||||
var me = CurrentPlayerId;
|
||||
|
||||
// Global-friends mode (admin toggle): synthesize a Friend row for
|
||||
// every other account WITHOUT persisting anything. Real rows are
|
||||
// overlaid so mute/favorite still apply and Blocked stays non-friend.
|
||||
if (await settings.IsGlobalFriendsEnabledAsync())
|
||||
return Ok(await BuildGlobalFriendListAsync(me));
|
||||
|
||||
var rels = await db.Relationships
|
||||
.Where(r => r.RequesterId == me || r.TargetId == me)
|
||||
.ToListAsync();
|
||||
@@ -79,6 +86,55 @@ public class RelationshipsController(DorkNetDbContext db, NotificationService no
|
||||
return Ok(wire);
|
||||
}
|
||||
|
||||
/// <summary>Synthesize the caller's friend list as "everyone" for the
|
||||
/// global-friends toggle. One Friend entry per other account; real rows
|
||||
/// are overlaid so a Block stays None and mute/favorite carry through.</summary>
|
||||
private async Task<List<Dictionary<string, object>>> BuildGlobalFriendListAsync(long me)
|
||||
{
|
||||
var realRows = await db.Relationships
|
||||
.Where(r => r.RequesterId == me || r.TargetId == me)
|
||||
.ToListAsync();
|
||||
var realByOther = new Dictionary<long, RelationshipEntity>();
|
||||
foreach (var r in realRows)
|
||||
realByOther[r.RequesterId == me ? r.TargetId : r.RequesterId] = r;
|
||||
|
||||
var others = await db.Players
|
||||
.Where(p => p.Id != me && p.Id != RelationshipQueries.SystemAccountId)
|
||||
.Select(p => p.Id)
|
||||
.ToListAsync();
|
||||
|
||||
var wire = new List<Dictionary<string, object>>(others.Count);
|
||||
foreach (var otherId in others)
|
||||
{
|
||||
if (realByOther.TryGetValue(otherId, out var row))
|
||||
{
|
||||
// A real block wins — that pair is genuinely not friends.
|
||||
if (row.Status == EntityStatus.Blocked) { wire.Add(EmptyRelationship(otherId)); continue; }
|
||||
wire.Add(SynthFriend(otherId, row, me));
|
||||
}
|
||||
else
|
||||
{
|
||||
wire.Add(SynthFriend(otherId, null, me));
|
||||
}
|
||||
}
|
||||
return wire;
|
||||
}
|
||||
|
||||
/// <summary>A synthetic Friend relationship for global-friends mode,
|
||||
/// carrying mute/ignore/favorite from the caller's real row when present.</summary>
|
||||
private static Dictionary<string, object> SynthFriend(long other, RelationshipEntity? real, long me)
|
||||
{
|
||||
var mineDir = real is not null && real.RequesterId == me;
|
||||
return new()
|
||||
{
|
||||
["PlayerID"] = (int)other,
|
||||
["RelationshipType"] = RT_Friend,
|
||||
["Muted"] = mineDir && real!.Muted ? 1 : 0,
|
||||
["Ignored"] = mineDir && real!.Ignored ? 1 : 0,
|
||||
["Favorited"] = mineDir && real!.Favorited ? 1 : 0,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>GET <c>api/relationships/v2/personaldetails/{playerId}</c>
|
||||
/// — single relationship row from the caller's perspective. Used
|
||||
/// by the watch's profile-card view to determine whether to show
|
||||
@@ -90,6 +146,14 @@ public class RelationshipsController(DorkNetDbContext db, NotificationService no
|
||||
var me = CurrentPlayerId;
|
||||
if (playerId <= 0 || playerId == me) return Ok(EmptyRelationship(playerId));
|
||||
var row = await FindAsync(me, playerId);
|
||||
|
||||
// Global-friends mode: report Friend for any real account that isn't
|
||||
// the system account and isn't explicitly blocked.
|
||||
if (playerId != RelationshipQueries.SystemAccountId
|
||||
&& (row is null || row.Status != EntityStatus.Blocked)
|
||||
&& await settings.IsGlobalFriendsEnabledAsync())
|
||||
return Ok(SynthFriend(playerId, row, me));
|
||||
|
||||
return Ok(row is null ? EmptyRelationship(playerId) : BuildRelationship(row, me));
|
||||
}
|
||||
|
||||
|
||||
@@ -1390,12 +1390,41 @@ public class AdminController(
|
||||
return Ok(new
|
||||
{
|
||||
row.SignupsDisabled,
|
||||
row.GlobalFriendsEnabled,
|
||||
row.WeeklyChallengesCompletedRequired,
|
||||
row.UpdatedAt,
|
||||
});
|
||||
}
|
||||
|
||||
public sealed record SignupsToggleRequest(bool Disabled);
|
||||
public sealed record GlobalFriendsToggleRequest(bool Enabled);
|
||||
|
||||
/// <summary>POST <c>api/admin/v1/settings/global-friends</c> — flip the
|
||||
/// "everyone is friends" toggle. No relationship rows are written; the
|
||||
/// friend graph is synthesized at read time (see
|
||||
/// <see cref="RelationshipQueries"/>). After flipping, broadcast
|
||||
/// <c>RelationshipsInvalid</c> to every connected watch so it re-fetches
|
||||
/// <c>api/relationships/v2/get</c> and shows the new state live — no
|
||||
/// relog (the 2020 watch handles that push via
|
||||
/// <c>Relationships.RefreshList</c>).</summary>
|
||||
[HttpPost("settings/global-friends")]
|
||||
public async Task<ActionResult> SetGlobalFriends([FromBody] GlobalFriendsToggleRequest body)
|
||||
{
|
||||
var row = await serverSettings.SetGlobalFriendsEnabledAsync(body.Enabled);
|
||||
await LogAsync(body.Enabled ? "global_friends_enabled" : "global_friends_disabled", "system", 0, "");
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
// Live refresh: every connected watch re-pulls its relationship list.
|
||||
await notifications.BroadcastAsync(DorkNet.Models.Notification.PushNotificationId.RelationshipsInvalid);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
row.SignupsDisabled,
|
||||
row.GlobalFriendsEnabled,
|
||||
row.WeeklyChallengesCompletedRequired,
|
||||
row.UpdatedAt,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>POST <c>api/admin/v1/settings/signups</c> — flip the
|
||||
/// server-wide account-creation kill switch. While disabled, both
|
||||
@@ -1412,6 +1441,7 @@ public class AdminController(
|
||||
return Ok(new
|
||||
{
|
||||
row.SignupsDisabled,
|
||||
row.GlobalFriendsEnabled,
|
||||
row.WeeklyChallengesCompletedRequired,
|
||||
row.UpdatedAt,
|
||||
});
|
||||
|
||||
@@ -40,6 +40,7 @@ public class GoToController(
|
||||
DorkNet.Server.Data.DorkNetDbContext db,
|
||||
NotificationService notifications,
|
||||
JoinTimeoutService joinTimeouts,
|
||||
ServerSettingsService serverSettings,
|
||||
ILogger<GoToController> logger) : ControllerBase
|
||||
{
|
||||
[HttpPost("/goto/room/{roomName}")]
|
||||
@@ -1005,11 +1006,10 @@ public class GoToController(
|
||||
|
||||
private async Task NotifyFriendsOfMoveAsync(long playerId, RoomInstanceDto room)
|
||||
{
|
||||
var friendIds = await db.Relationships
|
||||
.Where(r => r.Status == DorkNet.Server.Data.Entities.RelationshipStatus.Friend &&
|
||||
(r.RequesterId == playerId || r.TargetId == playerId))
|
||||
.Select(r => r.RequesterId == playerId ? r.TargetId : r.RequesterId)
|
||||
.ToListAsync();
|
||||
// Honors the global-friends toggle: with it on, every other player is
|
||||
// a "friend" and gets the presence ping (so the whole small server
|
||||
// sees each other's room moves).
|
||||
var friendIds = await RelationshipQueries.EffectiveFriendIdsAsync(db, serverSettings, playerId);
|
||||
logger.LogInformation(
|
||||
"[goto-fanout] player={PlayerId} moved to room={RoomId}/{RoomName} → notifying {FriendCount} friend(s)",
|
||||
playerId, room.RoomId, room.Name, friendIds.Count);
|
||||
|
||||
Reference in New Issue
Block a user