[backend/api] Add relations to user profile response (ISH-347)

This commit is contained in:
Laura Hausmann 2024-05-23 22:36:56 +02:00
parent 167fd5f0d6
commit ffa46eded6
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
3 changed files with 70 additions and 13 deletions

View file

@ -10,12 +10,24 @@ public class UserProfileRenderer(DatabaseContext db)
{ {
public async Task<UserProfileResponse> RenderOne(User user, User? localUser, UserRendererDto? data = null) public async Task<UserProfileResponse> 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 ffVisibility = user.UserProfile?.FFVisibility ?? UserProfile.UserProfileFFVisibility.Public;
var followers = ffVisibility switch var followers = ffVisibility switch
{ {
UserProfile.UserProfileFFVisibility.Public => user.FollowersCount, 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, UserProfile.UserProfileFFVisibility.Private => (int?)null,
_ => throw new ArgumentOutOfRangeException() _ => throw new ArgumentOutOfRangeException()
}; };
@ -23,7 +35,7 @@ public class UserProfileRenderer(DatabaseContext db)
var following = ffVisibility switch var following = ffVisibility switch
{ {
UserProfile.UserProfileFFVisibility.Public => user.FollowingCount, 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, UserProfile.UserProfileFFVisibility.Private => (int?)null,
_ => throw new ArgumentOutOfRangeException() _ => throw new ArgumentOutOfRangeException()
}; };
@ -43,27 +55,42 @@ public class UserProfileRenderer(DatabaseContext db)
Fields = fields?.ToList(), Fields = fields?.ToList(),
Location = user.UserProfile?.Location, Location = user.UserProfile?.Location,
Followers = followers, Followers = followers,
Following = following Following = following,
Relations = relations
}; };
} }
private async Task<List<string>> GetFollowing(IEnumerable<User> users, User? localUser) private async Task<Dictionary<string, RelationData>> GetRelations(IEnumerable<User> users, User? localUser)
{ {
var ids = users.Select(p => p.Id).ToList();
if (ids.Count == 0) return [];
if (localUser == null) return []; if (localUser == null) return [];
return await db.Followings.Where(p => p.Follower == localUser && users.Contains(p.Followee))
.Select(p => p.FolloweeId) return await db.Users
.ToListAsync(); .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<IEnumerable<UserProfileResponse>> RenderMany(IEnumerable<User> users, User? localUser) public async Task<IEnumerable<UserProfileResponse>> RenderMany(IEnumerable<User> users, User? localUser)
{ {
var userList = users.ToList(); 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(); return await userList.Select(p => RenderOne(p, localUser, data)).AwaitAllAsync();
} }
public class UserRendererDto public class UserRendererDto
{ {
public List<string>? Following; public Dictionary<string, RelationData>? Relations;
} }
} }

View file

@ -14,6 +14,8 @@ using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Controllers; namespace Iceshrimp.Backend.Controllers;
[ApiController] [ApiController]
[Authenticate]
[Authorize]
[EnableRateLimiting("sliding")] [EnableRateLimiting("sliding")]
[Route("/api/iceshrimp/users/{id}")] [Route("/api/iceshrimp/users/{id}")]
[Produces(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json)]
@ -51,7 +53,6 @@ public class UserController(
} }
[HttpGet("notes")] [HttpGet("notes")]
[Authenticate]
[LinkPagination(20, 80)] [LinkPagination(20, 80)]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<NoteResponse>))] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<NoteResponse>))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ErrorResponse))] [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ErrorResponse))]

View file

@ -1,3 +1,5 @@
using System.Text.Json.Serialization;
namespace Iceshrimp.Shared.Schemas; namespace Iceshrimp.Shared.Schemas;
public class UserProfileResponse public class UserProfileResponse
@ -9,6 +11,33 @@ public class UserProfileResponse
public required string? Bio { get; set; } public required string? Bio { get; set; }
public required int? Followers { get; set; } public required int? Followers { get; set; }
public required int? Following { 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 public class UserProfileField