diff --git a/Iceshrimp.Backend/Controllers/Renderers/UserProfileRenderer.cs b/Iceshrimp.Backend/Controllers/Renderers/UserProfileRenderer.cs index b359a85c..939baa34 100644 --- a/Iceshrimp.Backend/Controllers/Renderers/UserProfileRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Renderers/UserProfileRenderer.cs @@ -10,12 +10,24 @@ public class UserProfileRenderer(DatabaseContext db) { public async Task RenderOne(User user, User? localUser, UserRendererDto? data = null) { - var isFollowing = (data?.Following ?? await GetFollowing([user], localUser)).Contains(user.Id); + (data?.Relations ?? await GetRelations([user], localUser)).TryGetValue(user.Id, out var relations); + relations ??= new RelationData + { + UserId = user.Id, + IsSelf = user.Id == localUser?.Id, + IsFollowing = false, + IsFollowedBy = false, + IsBlocking = false, + IsMuting = false, + IsRequested = false, + IsRequestedBy = false + }; + var ffVisibility = user.UserProfile?.FFVisibility ?? UserProfile.UserProfileFFVisibility.Public; var followers = ffVisibility switch { UserProfile.UserProfileFFVisibility.Public => user.FollowersCount, - UserProfile.UserProfileFFVisibility.Followers => isFollowing ? user.FollowersCount : null, + UserProfile.UserProfileFFVisibility.Followers => relations.IsFollowing ? user.FollowersCount : null, UserProfile.UserProfileFFVisibility.Private => (int?)null, _ => throw new ArgumentOutOfRangeException() }; @@ -23,15 +35,15 @@ public class UserProfileRenderer(DatabaseContext db) var following = ffVisibility switch { UserProfile.UserProfileFFVisibility.Public => user.FollowingCount, - UserProfile.UserProfileFFVisibility.Followers => isFollowing ? user.FollowingCount : null, + UserProfile.UserProfileFFVisibility.Followers => relations.IsFollowing ? user.FollowingCount : null, UserProfile.UserProfileFFVisibility.Private => (int?)null, _ => throw new ArgumentOutOfRangeException() }; var fields = user.UserProfile?.Fields.Select(p => new UserProfileField { - Name = p.Name, - Value = p.Value, + Name = p.Name, + Value = p.Value, Verified = p.IsVerified }); @@ -43,27 +55,42 @@ public class UserProfileRenderer(DatabaseContext db) Fields = fields?.ToList(), Location = user.UserProfile?.Location, Followers = followers, - Following = following + Following = following, + Relations = relations }; } - private async Task> GetFollowing(IEnumerable users, User? localUser) + private async Task> GetRelations(IEnumerable users, User? localUser) { + var ids = users.Select(p => p.Id).ToList(); + if (ids.Count == 0) return []; if (localUser == null) return []; - return await db.Followings.Where(p => p.Follower == localUser && users.Contains(p.Followee)) - .Select(p => p.FolloweeId) - .ToListAsync(); + + return await db.Users + .Where(p => ids.Contains(p.Id)) + .Select(p => new RelationData + { + UserId = p.Id, + IsSelf = p.Id == localUser.Id, + IsFollowing = localUser.IsFollowing(p), + IsFollowedBy = localUser.IsFollowedBy(p), + IsBlocking = localUser.IsBlocking(p), + IsMuting = localUser.IsMuting(p), + IsRequested = localUser.IsRequested(p), + IsRequestedBy = localUser.IsRequestedBy(p) + }) + .ToDictionaryAsync(p => p.UserId, p => p); } public async Task> RenderMany(IEnumerable users, User? localUser) { var userList = users.ToList(); - var data = new UserRendererDto { Following = await GetFollowing(userList, localUser) }; + var data = new UserRendererDto { Relations = await GetRelations(userList, localUser) }; return await userList.Select(p => RenderOne(p, localUser, data)).AwaitAllAsync(); } public class UserRendererDto { - public List? Following; + public Dictionary? Relations; } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Controllers/UserController.cs b/Iceshrimp.Backend/Controllers/UserController.cs index 345701f0..1246218c 100644 --- a/Iceshrimp.Backend/Controllers/UserController.cs +++ b/Iceshrimp.Backend/Controllers/UserController.cs @@ -14,6 +14,8 @@ using Microsoft.EntityFrameworkCore; namespace Iceshrimp.Backend.Controllers; [ApiController] +[Authenticate] +[Authorize] [EnableRateLimiting("sliding")] [Route("/api/iceshrimp/users/{id}")] [Produces(MediaTypeNames.Application.Json)] @@ -51,7 +53,6 @@ public class UserController( } [HttpGet("notes")] - [Authenticate] [LinkPagination(20, 80)] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ErrorResponse))] diff --git a/Iceshrimp.Shared/Schemas/UserProfileResponse.cs b/Iceshrimp.Shared/Schemas/UserProfileResponse.cs index f886dd31..88a105e4 100644 --- a/Iceshrimp.Shared/Schemas/UserProfileResponse.cs +++ b/Iceshrimp.Shared/Schemas/UserProfileResponse.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace Iceshrimp.Shared.Schemas; public class UserProfileResponse @@ -9,6 +11,33 @@ public class UserProfileResponse public required string? Bio { get; set; } public required int? Followers { get; set; } public required int? Following { get; set; } + public required RelationData Relations { get; set; } +} + +public class RelationData +{ + [JsonIgnore] public required string UserId; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public required bool IsSelf { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public required bool IsFollowing { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public required bool IsFollowedBy { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public required bool IsRequested { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public required bool IsRequestedBy { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public required bool IsBlocking { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public required bool IsMuting { get; set; } } public class UserProfileField