[backend/masto-client] Fix concurrent DbContext use in UserRenderer

This commit is contained in:
Laura Hausmann 2025-02-06 14:37:45 +01:00
parent 685cb7d0b9
commit b9724e29fb
No known key found for this signature in database
GPG key ID: D044E84C5BE01605

View file

@ -18,15 +18,18 @@ public class UserRenderer(
{ {
private readonly string _transparent = $"https://{config.Value.WebDomain}/assets/transparent.png"; private readonly string _transparent = $"https://{config.Value.WebDomain}/assets/transparent.png";
public async Task<AccountEntity> RenderAsync( public Task<AccountEntity> RenderAsync(User user, UserProfile? profile, User? localUser, bool source = false)
User user, UserProfile? profile, User? localUser, IEnumerable<EmojiEntity>? emoji = null, bool source = false => RenderAsync(user, profile, localUser, null, source);
private async Task<AccountEntity> RenderAsync(
User user, UserProfile? profile, User? localUser, UserRendererDto? data = null, bool source = false
) )
{ {
var acct = user.Username; var acct = user.Username;
if (user.IsRemoteUser) if (user.IsRemoteUser)
acct += $"@{user.Host}"; acct += $"@{user.Host}";
var profileEmoji = emoji?.Where(p => user.Emojis.Contains(p.Id)).ToList() ?? await GetEmojiAsync([user]); var profileEmoji = data?.Emoji.Where(p => user.Emojis.Contains(p.Id)).ToList() ?? await GetEmojiAsync([user]);
var mentions = profile?.Mentions ?? []; var mentions = profile?.Mentions ?? [];
var fields = profile != null var fields = profile != null
? await profile.Fields ? await profile.Fields
@ -45,14 +48,8 @@ public class UserRenderer(
? profile?.Fields.Select(p => new Field { Name = p.Name, Value = p.Value }).ToList() ?? [] ? profile?.Fields.Select(p => new Field { Name = p.Name, Value = p.Value }).ToList() ?? []
: []; : [];
user.Avatar ??= await db.Users.Where(p => p.Id == user.Id) var avatarAlt = data?.AvatarAlt.GetValueOrDefault(user.Id);
.Include(p => p.Avatar) var bannerAlt = data?.BannerAlt.GetValueOrDefault(user.Id);
.Select(p => p.Avatar)
.FirstOrDefaultAsync();
user.Banner ??= await db.Users.Where(p => p.Id == user.Id)
.Include(p => p.Banner)
.Select(p => p.Banner)
.FirstOrDefaultAsync();
var res = new AccountEntity var res = new AccountEntity
{ {
@ -71,10 +68,10 @@ public class UserRenderer(
Url = profile?.Url ?? user.Uri ?? user.GetPublicUrl(config.Value), Url = profile?.Url ?? user.Uri ?? user.GetPublicUrl(config.Value),
Uri = user.Uri ?? user.GetPublicUri(config.Value), Uri = user.Uri ?? user.GetPublicUri(config.Value),
AvatarStaticUrl = user.GetAvatarUrl(config.Value), //TODO AvatarStaticUrl = user.GetAvatarUrl(config.Value), //TODO
AvatarDescription = user.Avatar?.Comment ?? "", AvatarDescription = avatarAlt ?? "",
HeaderUrl = user.GetBannerUrl(config.Value) ?? _transparent, HeaderUrl = user.GetBannerUrl(config.Value) ?? _transparent,
HeaderStaticUrl = user.GetBannerUrl(config.Value) ?? _transparent, //TODO HeaderStaticUrl = user.GetBannerUrl(config.Value) ?? _transparent, //TODO
HeaderDescription = user.Banner?.Comment ?? "", HeaderDescription = bannerAlt ?? "",
MovedToAccount = null, //TODO MovedToAccount = null, //TODO
IsBot = user.IsBot, IsBot = user.IsBot,
IsDiscoverable = user.IsExplorable, IsDiscoverable = user.IsExplorable,
@ -98,8 +95,9 @@ public class UserRenderer(
Fields = fieldsSource, Fields = fieldsSource,
Language = "", Language = "",
Note = profile?.Description ?? "", Note = profile?.Description ?? "",
Privacy = StatusEntity.EncodeVisibility(user.UserSettings?.DefaultNoteVisibility ?? Privacy =
Note.NoteVisibility.Public), StatusEntity.EncodeVisibility(user.UserSettings?.DefaultNoteVisibility
?? Note.NoteVisibility.Public),
Sensitive = false, Sensitive = false,
FollowRequestCount = await db.FollowRequests.CountAsync(p => p.Followee == user) FollowRequestCount = await db.FollowRequests.CountAsync(p => p.Followee == user)
}; };
@ -127,16 +125,55 @@ public class UserRenderer(
.ToListAsync(); .ToListAsync();
} }
private async Task<Dictionary<string, string?>> GetAvatarAltAsync(IEnumerable<User> users)
{
var ids = users.Select(p => p.Id).ToList();
return await db.Users
.Where(p => ids.Contains(p.Id))
.Include(p => p.Avatar)
.ToDictionaryAsync(p => p.Id, p => p.Avatar?.Comment);
}
private async Task<Dictionary<string, string?>> GetBannerAltAsync(IEnumerable<User> users)
{
var ids = users.Select(p => p.Id).ToList();
return await db.Users
.Where(p => ids.Contains(p.Id))
.Include(p => p.Banner)
.ToDictionaryAsync(p => p.Id, p => p.Banner?.Comment);
}
public async Task<AccountEntity> RenderAsync(User user, User? localUser, List<EmojiEntity>? emoji = null) public async Task<AccountEntity> RenderAsync(User user, User? localUser, List<EmojiEntity>? emoji = null)
{ {
return await RenderAsync(user, user.UserProfile, localUser, emoji); var data = new UserRendererDto
{
Emoji = emoji ?? await GetEmojiAsync([user]),
AvatarAlt = await GetAvatarAltAsync([user]),
BannerAlt = await GetBannerAltAsync([user])
};
return await RenderAsync(user, user.UserProfile, localUser, data);
} }
public async Task<IEnumerable<AccountEntity>> RenderManyAsync(IEnumerable<User> users, User? localUser) public async Task<IEnumerable<AccountEntity>> RenderManyAsync(IEnumerable<User> users, User? localUser)
{ {
var userList = users.ToList(); var userList = users.ToList();
if (userList.Count == 0) return []; if (userList.Count == 0) return [];
var emoji = await GetEmojiAsync(userList);
return await userList.Select(p => RenderAsync(p, localUser, emoji)).AwaitAllAsync(); var data = new UserRendererDto
{
Emoji = await GetEmojiAsync(userList),
AvatarAlt = await GetAvatarAltAsync(userList),
BannerAlt = await GetBannerAltAsync(userList)
};
return await userList.Select(p => RenderAsync(p, p.UserProfile, localUser, data)).AwaitAllAsync();
}
private class UserRendererDto
{
public required List<EmojiEntity> Emoji;
public required Dictionary<string, string?> AvatarAlt;
public required Dictionary<string, string?> BannerAlt;
} }
} }