diff --git a/apps/server-net/src/Modules/Chats/Application/Chats/GetChatById/GetChatById.cs b/apps/server-net/src/Modules/Chats/Application/Chats/GetChatById/GetChatById.cs index ea5d3eb..56a0b74 100644 --- a/apps/server-net/src/Modules/Chats/Application/Chats/GetChatById/GetChatById.cs +++ b/apps/server-net/src/Modules/Chats/Application/Chats/GetChatById/GetChatById.cs @@ -8,7 +8,6 @@ using Knot.Shared.Kernel; using Knot.Modules.Chats.Application.DTOs; using Knot.Modules.Chats.Domain; using Knot.Modules.Chats.Application.Abstractions; -using Knot.Modules.Identity.Domain; namespace Knot.Modules.Chats.Application.Chats.GetChatById; @@ -17,13 +16,13 @@ public record GetChatByIdQuery(Guid ChatId) : IQuery; internal sealed class GetChatByIdQueryHandler : IQueryHandler { private readonly IChatRepository _chatRepository; - private readonly IUserRepository _userRepository; + private readonly IUserDisplayNameProvider _userProvider; private readonly IMessageRepository _messageRepository; - public GetChatByIdQueryHandler(IChatRepository chatRepository, IUserRepository userRepository, IMessageRepository messageRepository) + public GetChatByIdQueryHandler(IChatRepository chatRepository, IUserDisplayNameProvider userProvider, IMessageRepository messageRepository) { _chatRepository = chatRepository; - _userRepository = userRepository; + _userProvider = userProvider; _messageRepository = messageRepository; } @@ -32,10 +31,23 @@ internal sealed class GetChatByIdQueryHandler : IQueryHandler(null); + var userIdsToFetch = new HashSet(); + foreach (var m in chat.Members) userIdsToFetch.Add(m.UserId); + + var chatMessages = await _messageRepository.GetChatMessagesAsync(chat.Id, 1, 0, cancellationToken); + var mFirst = chatMessages.FirstOrDefault(); + if (mFirst != null) + { + userIdsToFetch.Add(mFirst.SenderId); + foreach (var r in mFirst.Reactions) userIdsToFetch.Add(r.UserId); + } + + var usersInfo = await _userProvider.GetUsersInfoAsync(userIdsToFetch, cancellationToken); + var members = new List(); foreach (var m in chat.Members) { - var user = await _userRepository.GetByIdAsync(m.UserId, cancellationToken); + usersInfo.TryGetValue(m.UserId, out var user); members.Add(new ChatMemberDto( m.Id, m.UserId, @@ -52,17 +64,16 @@ internal sealed class GetChatByIdQueryHandler : IQueryHandler(); if (chatMessages.Any()) { - var m = chatMessages.First(); - var senderObj = await _userRepository.GetByIdAsync(m.SenderId, cancellationToken); + var m = mFirst; + usersInfo.TryGetValue(m.SenderId, out var senderObj); var reactionsWithUser = new List(); foreach (var r in m.Reactions) { - var rUser = await _userRepository.GetByIdAsync(r.UserId, cancellationToken); + usersInfo.TryGetValue(r.UserId, out var rUser); reactionsWithUser.Add(new ReactionDto( r.Id, r.Emoji, diff --git a/apps/server-net/src/Modules/Chats/Application/Chats/GetChats/GetChats.cs b/apps/server-net/src/Modules/Chats/Application/Chats/GetChats/GetChats.cs index b8032d8..392161e 100644 --- a/apps/server-net/src/Modules/Chats/Application/Chats/GetChats/GetChats.cs +++ b/apps/server-net/src/Modules/Chats/Application/Chats/GetChats/GetChats.cs @@ -8,7 +8,7 @@ using Knot.Shared.Kernel; using Knot.Modules.Chats.Application.DTOs; using Knot.Modules.Chats.Domain; using Knot.Modules.Chats.Application.Abstractions; -using Knot.Modules.Identity.Domain; + namespace Knot.Modules.Chats.Application.Chats.GetChats; @@ -17,13 +17,13 @@ public record GetChatsQuery(Guid UserId) : IQuery>; internal sealed class GetChatsQueryHandler : IQueryHandler> { private readonly IChatRepository _chatRepository; - private readonly IUserRepository _userRepository; + private readonly IUserDisplayNameProvider _userProvider; private readonly IMessageRepository _messageRepository; - public GetChatsQueryHandler(IChatRepository chatRepository, IUserRepository userRepository, IMessageRepository messageRepository) + public GetChatsQueryHandler(IChatRepository chatRepository, IUserDisplayNameProvider userProvider, IMessageRepository messageRepository) { _chatRepository = chatRepository; - _userRepository = userRepository; + _userProvider = userProvider; _messageRepository = messageRepository; } @@ -35,16 +35,23 @@ internal sealed class GetChatsQueryHandler : IQueryHandler(); + foreach (var m in c.Members) userIdsToFetch.Add(m.UserId); + + var chatMessages = await _messageRepository.GetChatMessagesAsync(c.Id, 1, 0, cancellationToken); + var mFirst = chatMessages.FirstOrDefault(); + if (mFirst != null) { - if (hasFavorites) continue; - hasFavorites = true; + userIdsToFetch.Add(mFirst.SenderId); + foreach (var r in mFirst.Reactions) userIdsToFetch.Add(r.UserId); } + var usersInfo = await _userProvider.GetUsersInfoAsync(userIdsToFetch, cancellationToken); + var members = new List(); foreach (var m in c.Members) { - var user = await _userRepository.GetByIdAsync(m.UserId, cancellationToken); + usersInfo.TryGetValue(m.UserId, out var user); members.Add(new ChatMemberDto( m.Id, m.UserId, @@ -61,18 +68,17 @@ internal sealed class GetChatsQueryHandler : IQueryHandler(); if (chatMessages.Any()) { - var m = chatMessages.First(); - var senderObj = await _userRepository.GetByIdAsync(m.SenderId, cancellationToken); + var m = mFirst; + usersInfo.TryGetValue(m.SenderId, out var senderObj); var reactionsWithUser = new List(); foreach (var r in m.Reactions) { - var rUser = await _userRepository.GetByIdAsync(r.UserId, cancellationToken); + usersInfo.TryGetValue(r.UserId, out var rUser); reactionsWithUser.Add(new ReactionDto( r.Id, r.Emoji, diff --git a/apps/server-net/src/Modules/Chats/Application/Messages/GetMessages/GetMessagesQuery.cs b/apps/server-net/src/Modules/Chats/Application/Messages/GetMessages/GetMessagesQuery.cs index 0b79734..ff26c99 100644 --- a/apps/server-net/src/Modules/Chats/Application/Messages/GetMessages/GetMessagesQuery.cs +++ b/apps/server-net/src/Modules/Chats/Application/Messages/GetMessages/GetMessagesQuery.cs @@ -7,7 +7,6 @@ using MediatR; using Knot.Shared.Kernel; using Knot.Modules.Chats.Application.DTOs; using Knot.Modules.Chats.Domain; -using Knot.Modules.Identity.Domain; namespace Knot.Modules.Chats.Application.Messages.GetMessages; @@ -16,12 +15,12 @@ public record GetMessagesQuery(Guid UserId, Guid ChatId, string? Cursor) : IQuer internal sealed class GetMessagesQueryHandler : IQueryHandler> { private readonly IMessageRepository _messageRepository; - private readonly IUserRepository _userRepository; + private readonly IUserDisplayNameProvider _userProvider; - public GetMessagesQueryHandler(IMessageRepository messageRepository, IUserRepository userRepository) + public GetMessagesQueryHandler(IMessageRepository messageRepository, IUserDisplayNameProvider userProvider) { _messageRepository = messageRepository; - _userRepository = userRepository; + _userProvider = userProvider; } public async Task>> Handle(GetMessagesQuery request, CancellationToken cancellationToken) @@ -33,58 +32,57 @@ internal sealed class GetMessagesQueryHandler : IQueryHandler(); var result = new List(); + var userIdsToFetch = new HashSet(); + var replyMessages = new Dictionary(); + foreach (var m in messages) { if (m.DeletedByUsers.Contains(request.UserId)) continue; - if (!senders.ContainsKey(m.SenderId)) - { - var user = await _userRepository.GetByIdAsync(m.SenderId, cancellationToken); - if (user != null) senders[m.SenderId] = user; - } + userIdsToFetch.Add(m.SenderId); + if (m.ForwardedFromId.HasValue) userIdsToFetch.Add(m.ForwardedFromId.Value); + + foreach (var r in m.Reactions) + userIdsToFetch.Add(r.UserId); - if (m.ForwardedFromId.HasValue && !senders.ContainsKey(m.ForwardedFromId.Value)) - { - var fuser = await _userRepository.GetByIdAsync(m.ForwardedFromId.Value, cancellationToken); - if (fuser != null) senders[m.ForwardedFromId.Value] = fuser; - } - - ReplyToMessageDto? replyToObj = null; if (m.ReplyToId.HasValue) { var replyMsg = await _messageRepository.GetByIdAsync(m.ReplyToId.Value, cancellationToken); if (replyMsg != null) { - if (!senders.ContainsKey(replyMsg.SenderId)) - { - var replySender = await _userRepository.GetByIdAsync(replyMsg.SenderId, cancellationToken); - if (replySender != null) senders[replyMsg.SenderId] = replySender; - } - var senderObj = senders.TryGetValue(replyMsg.SenderId, out var rs) - ? new MessageSenderDto(rs.Id, rs.Username, rs.DisplayName, rs.Avatar) - : null; - - replyToObj = new ReplyToMessageDto( - replyMsg.Id, - replyMsg.Content, - replyMsg.IsDeleted, - replyMsg.Media.Select(rm => new MediaDto(rm.Id, rm.Type, rm.Url, rm.Filename, rm.Size)).ToList(), - senderObj - ); + replyMessages[replyMsg.Id] = replyMsg; + userIdsToFetch.Add(replyMsg.SenderId); } } + } + + var senders = await _userProvider.GetUsersInfoAsync(userIdsToFetch, cancellationToken); + + foreach (var m in messages) + { + if (m.DeletedByUsers.Contains(request.UserId)) continue; + + ReplyToMessageDto? replyToObj = null; + if (m.ReplyToId.HasValue && replyMessages.TryGetValue(m.ReplyToId.Value, out var replyMsg)) + { + var senderObj = senders.TryGetValue(replyMsg.SenderId, out var rs) + ? new MessageSenderDto(rs.Id, rs.Username, rs.DisplayName, rs.Avatar) + : null; + + replyToObj = new ReplyToMessageDto( + replyMsg.Id, + replyMsg.Content, + replyMsg.IsDeleted, + replyMsg.Media.Select(rm => new MediaDto(rm.Id, rm.Type, rm.Url, rm.Filename, rm.Size)).ToList(), + senderObj + ); + } var reactionsWithUser = new List(); foreach (var r in m.Reactions) { - if (!senders.ContainsKey(r.UserId)) - { - var rUser = await _userRepository.GetByIdAsync(r.UserId, cancellationToken); - if (rUser != null) senders[r.UserId] = rUser; - } var userObj = senders.TryGetValue(r.UserId, out var ru) ? new MessageSenderDto(ru.Id, ru.Username, ru.DisplayName, ru.Avatar) : new MessageSenderDto(r.UserId, "unknown", "Unknown", null); diff --git a/apps/server-net/src/Modules/Chats/Application/Messages/GetSharedMedia/GetSharedMediaQuery.cs b/apps/server-net/src/Modules/Chats/Application/Messages/GetSharedMedia/GetSharedMediaQuery.cs index cf91437..73bb1a8 100644 --- a/apps/server-net/src/Modules/Chats/Application/Messages/GetSharedMedia/GetSharedMediaQuery.cs +++ b/apps/server-net/src/Modules/Chats/Application/Messages/GetSharedMedia/GetSharedMediaQuery.cs @@ -8,7 +8,6 @@ using MediatR; using Knot.Shared.Kernel; using Knot.Modules.Chats.Application.DTOs; using Knot.Modules.Chats.Domain; -using Knot.Modules.Identity.Domain; namespace Knot.Modules.Chats.Application.Messages.GetSharedMedia; @@ -17,12 +16,12 @@ public record GetSharedMediaQuery(Guid UserId, Guid ChatId, string? Type) : IQue internal sealed class GetSharedMediaQueryHandler : IQueryHandler> { private readonly IMessageRepository _messageRepository; - private readonly IUserRepository _userRepository; + private readonly IUserDisplayNameProvider _userProvider; - public GetSharedMediaQueryHandler(IMessageRepository messageRepository, IUserRepository userRepository) + public GetSharedMediaQueryHandler(IMessageRepository messageRepository, IUserDisplayNameProvider userProvider) { _messageRepository = messageRepository; - _userRepository = userRepository; + _userProvider = userProvider; } public async Task>> Handle(GetSharedMediaQuery request, CancellationToken cancellationToken) @@ -33,6 +32,9 @@ internal sealed class GetSharedMediaQueryHandler : IQueryHandler(); var filterType = request.Type?.ToLower(); + var userIds = messages.Select(m => m.SenderId).Distinct(); + var senders = await _userProvider.GetUsersInfoAsync(userIds, cancellationToken); + foreach (var m in messages) { if (filterType == "links") @@ -44,7 +46,7 @@ internal sealed class GetSharedMediaQueryHandler : IQueryHandler> { private readonly IMessageRepository _messageRepository; - private readonly IUserRepository _userRepository; + private readonly IUserDisplayNameProvider _userProvider; - public SearchMessagesQueryHandler(IMessageRepository messageRepository, IUserRepository userRepository) + public SearchMessagesQueryHandler(IMessageRepository messageRepository, IUserDisplayNameProvider userProvider) { _messageRepository = messageRepository; - _userRepository = userRepository; + _userProvider = userProvider; } public async Task>> Handle(SearchMessagesQuery request, CancellationToken cancellationToken) @@ -32,12 +31,7 @@ internal sealed class SearchMessagesQueryHandler : IQueryHandler m.SenderId).ToList(); userIds.AddRange(messages.Where(m => m.ForwardedFromId.HasValue).Select(m => m.ForwardedFromId!.Value)); - var senders = new Dictionary(); - foreach (var id in userIds.Distinct()) - { - var user = await _userRepository.GetByIdAsync(id, cancellationToken); - if (user != null) senders[id] = user; - } + var senders = await _userProvider.GetUsersInfoAsync(userIds.Distinct(), cancellationToken); var result = messages.Select(m => new SearchMessageDto( m.Id, diff --git a/apps/server-net/src/Modules/Chats/Knot.Modules.Chats.csproj b/apps/server-net/src/Modules/Chats/Knot.Modules.Chats.csproj index be0a51d..66029ca 100644 --- a/apps/server-net/src/Modules/Chats/Knot.Modules.Chats.csproj +++ b/apps/server-net/src/Modules/Chats/Knot.Modules.Chats.csproj @@ -2,7 +2,6 @@ - diff --git a/apps/server-net/src/Modules/Identity/Domain/IUserRepository.cs b/apps/server-net/src/Modules/Identity/Domain/IUserRepository.cs index a3c5448..cdf77d6 100644 --- a/apps/server-net/src/Modules/Identity/Domain/IUserRepository.cs +++ b/apps/server-net/src/Modules/Identity/Domain/IUserRepository.cs @@ -8,6 +8,7 @@ namespace Knot.Modules.Identity.Domain; public interface IUserRepository { Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); + Task> GetByIdsAsync(IEnumerable ids, CancellationToken cancellationToken = default); Task GetByUsernameAsync(string username, CancellationToken cancellationToken = default); Task IsUsernameUniqueAsync(string username, CancellationToken cancellationToken = default); Task> SearchUsersAsync(string query, CancellationToken cancellationToken = default); diff --git a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/UserRepository.cs b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/UserRepository.cs index 1bf32d4..98f2616 100644 --- a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/UserRepository.cs +++ b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/UserRepository.cs @@ -20,6 +20,11 @@ public sealed class UserRepository : IUserRepository return await _context.Users.FirstOrDefaultAsync(u => u.Id == id, cancellationToken); } + public async Task> GetByIdsAsync(IEnumerable ids, CancellationToken cancellationToken = default) + { + return await _context.Users.Where(u => ids.Contains(u.Id)).ToListAsync(cancellationToken); + } + public async Task GetByUsernameAsync(string username, CancellationToken cancellationToken = default) { return await _context.Users.FirstOrDefaultAsync(u => u.Username == username, cancellationToken); diff --git a/apps/server-net/src/Modules/Identity/Infrastructure/Services/UserDisplayNameProvider.cs b/apps/server-net/src/Modules/Identity/Infrastructure/Services/UserDisplayNameProvider.cs index 3e87fa9..744d6c4 100644 --- a/apps/server-net/src/Modules/Identity/Infrastructure/Services/UserDisplayNameProvider.cs +++ b/apps/server-net/src/Modules/Identity/Infrastructure/Services/UserDisplayNameProvider.cs @@ -24,4 +24,15 @@ public sealed class UserDisplayNameProvider : IUserDisplayNameProvider if (user == null) return null; return new UserInfo(user.Id, user.Username, user.DisplayName, user.Avatar); } + + public async Task> GetUsersInfoAsync(IEnumerable userIds, CancellationToken ct = default) + { + var users = await _userRepository.GetByIdsAsync(userIds, ct); + var result = new System.Collections.Generic.Dictionary(); + foreach (var user in users) + { + result[user.Id] = new UserInfo(user.Id, user.Username, user.DisplayName, user.Avatar); + } + return result; + } } diff --git a/apps/server-net/src/Shared/Knot.Shared.Kernel/IUserDisplayNameProvider.cs b/apps/server-net/src/Shared/Knot.Shared.Kernel/IUserDisplayNameProvider.cs index 5147d9d..1c5d15f 100644 --- a/apps/server-net/src/Shared/Knot.Shared.Kernel/IUserDisplayNameProvider.cs +++ b/apps/server-net/src/Shared/Knot.Shared.Kernel/IUserDisplayNameProvider.cs @@ -6,4 +6,5 @@ public interface IUserDisplayNameProvider { Task GetDisplayNameAsync(Guid userId, CancellationToken ct = default); Task GetUserInfoAsync(Guid userId, CancellationToken ct = default); + Task> GetUsersInfoAsync(IEnumerable userIds, CancellationToken ct = default); }