diff --git a/DysonNetwork.Sphere/ActivityPub/ActivityPubFollowController.cs b/DysonNetwork.Sphere/ActivityPub/ActivityPubFollowController.cs index efd78bc..525aa3e 100644 --- a/DysonNetwork.Sphere/ActivityPub/ActivityPubFollowController.cs +++ b/DysonNetwork.Sphere/ActivityPub/ActivityPubFollowController.cs @@ -7,6 +7,43 @@ using NodaTime; namespace DysonNetwork.Sphere.ActivityPub; +public class FediverseActorWithFollowStatus : SnFediverseActor +{ + public bool IsFollowing { get; set; } + + public static FediverseActorWithFollowStatus FromActor(SnFediverseActor actor, bool isFollowing = false) + { + return new FediverseActorWithFollowStatus + { + Id = actor.Id, + Type = actor.Type, + Uri = actor.Uri, + Username = actor.Username, + DisplayName = actor.DisplayName, + Bio = actor.Bio, + InboxUri = actor.InboxUri, + OutboxUri = actor.OutboxUri, + FollowersUri = actor.FollowersUri, + FollowingUri = actor.FollowingUri, + FeaturedUri = actor.FeaturedUri, + PublicKeyId = actor.PublicKeyId, + PublicKey = actor.PublicKey, + Metadata = actor.Metadata, + AvatarUrl = actor.AvatarUrl, + HeaderUrl = actor.HeaderUrl, + IsBot = actor.IsBot, + IsLocked = actor.IsLocked, + IsDiscoverable = actor.IsDiscoverable, + InstanceId = actor.InstanceId, + Instance = actor.Instance, + LastFetchedAt = actor.LastFetchedAt, + LastActivityAt = actor.LastActivityAt, + PublisherId = actor.PublisherId, + IsFollowing = isFollowing + }; + } +} + [ApiController] [Route("/api/activitypub")] [Authorize] @@ -90,8 +127,9 @@ public class ActivityPubFollowController( } [HttpGet("following")] - public async Task>> GetFollowing( - [FromQuery] int limit = 50 + public async Task>> GetFollowing( + [FromQuery] int take = 50, + [FromQuery] int offset = 0 ) { var currentUser = GetCurrentUser(); @@ -104,7 +142,16 @@ public class ActivityPubFollowController( .FirstOrDefaultAsync(); if (publisher == null) - return Ok(new List()); + { + Response.Headers.Append("X-Total", "0"); + return Ok(new List()); + } + + var totalCount = await db.FediverseRelationships + .CountAsync(r => + r.Actor.PublisherId == publisher.Id && + r.IsFollowing && + r.State == RelationshipState.Accepted); var actors = await db.FediverseRelationships .Include(r => r.TargetActor) @@ -114,16 +161,21 @@ public class ActivityPubFollowController( r.IsFollowing && r.State == RelationshipState.Accepted) .OrderByDescending(r => r.FollowedAt) + .Skip(offset) + .Take(take) .Select(r => r.TargetActor) - .Take(limit) .ToListAsync(); - return Ok(actors); + var result = actors.Select(a => FediverseActorWithFollowStatus.FromActor(a, true)).ToList(); + + Response.Headers.Append("X-Total", totalCount.ToString()); + return Ok(result); } [HttpGet("followers")] - public async Task>> GetFollowers( - [FromQuery] int limit = 50 + public async Task>> GetFollowers( + [FromQuery] int take = 50, + [FromQuery] int offset = 0 ) { var currentUser = GetCurrentUser(); @@ -136,7 +188,16 @@ public class ActivityPubFollowController( .FirstOrDefaultAsync(); if (publisher == null) - return Ok(new List()); + { + Response.Headers.Append("X-Total", "0"); + return Ok(new List()); + } + + var totalCount = await db.FediverseRelationships + .CountAsync(r => + r.Actor.PublisherId == publisher.Id && + r.IsFollowedBy && + r.State == RelationshipState.Accepted); var actors = await db.FediverseRelationships .Include(r => r.Actor) @@ -146,15 +207,19 @@ public class ActivityPubFollowController( r.IsFollowedBy && r.State == RelationshipState.Accepted) .OrderByDescending(r => r.FollowedAt ?? r.CreatedAt) + .Skip(offset) + .Take(take) .Select(r => r.Actor) - .Take(limit) .ToListAsync(); - return Ok(actors); + var result = await GetActorsWithFollowStatusAsync(actors, publisher.Id); + + Response.Headers.Append("X-Total", totalCount.ToString()); + return Ok(result); } [HttpGet("search")] - public async Task>> SearchRemoteUsers( + public async Task>> SearchRemoteUsers( [FromQuery] string query, [FromQuery] int limit = 20 ) @@ -164,7 +229,21 @@ public class ActivityPubFollowController( var actors = await discSrv.SearchActorsAsync(query, limit, includeRemoteDiscovery: true); - return Ok(actors); + var currentUser = GetCurrentUser(); + if (currentUser == null) + return Ok(actors.Select(a => FediverseActorWithFollowStatus.FromActor(a)).ToList()); + + var publisher = await db.Publishers + .Include(p => p.Members) + .Where(p => p.Members.Any(m => m.AccountId == currentUser)) + .FirstOrDefaultAsync(); + + if (publisher == null) + return Ok(actors.Select(a => FediverseActorWithFollowStatus.FromActor(a)).ToList()); + + var result = await GetActorsWithFollowStatusAsync(actors, publisher.Id); + + return Ok(result); } [HttpGet("relationships")] @@ -344,6 +423,41 @@ public class ActivityPubFollowController( } } + private async Task> GetActorsWithFollowStatusAsync( + List actors, + Guid publisherId + ) + { + if (actors.Count == 0) + return new List(); + + var actorIds = actors.Select(a => a.Id).ToList(); + + var userActor = await db.FediverseActors + .Where(a => a.PublisherId == publisherId) + .FirstOrDefaultAsync(); + + if (userActor == null) + return actors.Select(a => FediverseActorWithFollowStatus.FromActor(a, false)).ToList(); + + var followingActorIds = await db.FediverseRelationships + .Where(r => + r.ActorId == userActor.Id && + actorIds.Contains(r.TargetActorId) && + r.IsFollowing && + r.State == RelationshipState.Accepted) + .Select(r => r.TargetActorId) + .ToListAsync(); + + var result = actors.Select(actor => + { + var isFollowing = followingActorIds.Contains(actor.Id); + return FediverseActorWithFollowStatus.FromActor(actor, isFollowing); + }).ToList(); + + return result; + } + private Guid? GetCurrentUser() { HttpContext.Items.TryGetValue("CurrentUser", out var currentUser);