diff --git a/Application/GrpcClients/CommonRelationshipClient.cs b/Application/GrpcClients/CommonRelationshipClient.cs index 81c23d6..7c68c01 100644 --- a/Application/GrpcClients/CommonRelationshipClient.cs +++ b/Application/GrpcClients/CommonRelationshipClient.cs @@ -218,7 +218,7 @@ public async Task> GetAllBlockerIds(string profileId) throw new BaseException(BaseError.RELATIONSHIP_NOT_FOUND, StatusCodes.Status404NotFound); } } - + public async Task> GetAllBlockeeIds(string profileId) { try @@ -237,6 +237,24 @@ public async Task> GetAllBlockeeIds(string profileId) throw new BaseException(BaseError.RELATIONSHIP_NOT_FOUND, StatusCodes.Status404NotFound); } } + public async Task> GetFriendsOfMutualFriends(string profileId) + { + try + { + logger.LogInformation("Starting get mutual friend list"); + var response = await client.getFriendsOfMutualFriendsAsync(new ProfileRequest + { + Id = profileId + }); + // Call the gRPC server to introspect the token + return response.ProfileIdsWithMutualCounts; + } + catch (Exception e) + { + logger.LogError(e.Message); + throw new BaseException(BaseError.RELATIONSHIP_NOT_FOUND, StatusCodes.Status404NotFound); + } + } public async Task> CountMutualFriends(string profileId, IList profileIds) { try diff --git a/Application/Protos/relationship.proto b/Application/Protos/relationship.proto index 10e9de7..2feeb09 100644 --- a/Application/Protos/relationship.proto +++ b/Application/Protos/relationship.proto @@ -70,6 +70,8 @@ service RelationshipService { rpc countMutualFriends(MutualFriendCountRequest) returns (MutualFriendCountResponse) {} + rpc getFriendsOfMutualFriends(ProfileRequest) returns (MutualFriendCountResponse) {} + rpc getAllPendingRequestIds(ProfileRequest) returns (ProfileIdsResponse) {} rpc getAllRequestIds(ProfileRequest) returns (ProfileIdsResponse) {} diff --git a/Profile.Application/IServices/IUserProfileService.cs b/Profile.Application/IServices/IUserProfileService.cs index 70f8044..64c79b7 100644 --- a/Profile.Application/IServices/IUserProfileService.cs +++ b/Profile.Application/IServices/IUserProfileService.cs @@ -17,6 +17,8 @@ public interface IUserProfileService public Task Update(UserProfile userProfile); + public Task> SearchFriend(string keyword, string profileId, string cursor, int limit); + public Task> GetFriendSuggestions(string profileId, string cursor, int limit); public Task> GetFriendRequests(string profileId, string cursor, int limit); diff --git a/Profile.Application/Services/UserProfileService.cs b/Profile.Application/Services/UserProfileService.cs index daeb596..09990c7 100644 --- a/Profile.Application/Services/UserProfileService.cs +++ b/Profile.Application/Services/UserProfileService.cs @@ -166,26 +166,49 @@ public async Task> GetFriendSentRequests(string p public async Task> GetFriendSuggestions(string profileId, string cursor, int limit) { - IList followeeIds = await relationshipClient.GetAllFolloweeIds(profileId); - IList friendIds = await relationshipClient.GetAllFriendIds(profileId); + //IList followeeIds = await relationshipClient.GetAllFolloweeIds(profileId); + //IList friendIds = await relationshipClient.GetAllFriendIds(profileId); + var friendsOfMutualFriends = await relationshipClient.GetFriendsOfMutualFriends(profileId); IList pendingRequests = await relationshipClient.GetAllPendingRequestIds(profileId); IList blockerIds = await relationshipClient.GetAllBlockerIds(profileId.ToString()); IList blockeeIds = await relationshipClient.GetAllBlockeeIds(profileId.ToString()); + var mutualFriendIds = friendsOfMutualFriends + .Select(item => item.ProfileId) + .ToList(); var specification = new SpecificationWithCursor { Criteria = userProfile => - !userProfile.Id.Equals(Guid.Parse(profileId)) - && !friendIds.Contains(userProfile.Id.ToString()) - && !pendingRequests.Contains(userProfile.Id.ToString()) - && !blockerIds.Concat(blockeeIds).Contains(userProfile.Id.ToString()) - && !blockerIds.Concat(blockeeIds).Contains(userProfile.Id.ToString()), + mutualFriendIds.Contains(userProfile.Id.ToString()) + & !userProfile.Id.Equals(Guid.Parse(profileId)) + //&& !friendIds.Contains(userProfile.Id.ToString()) + & !pendingRequests.Contains(userProfile.Id.ToString()) + & !blockerIds.Concat(blockeeIds).Contains(userProfile.Id.ToString()) + & !blockerIds.Concat(blockeeIds).Contains(userProfile.Id.ToString()), Cursor = cursor, Limit = limit }; return await userProfileRepository.GetPagedAsync(specification); } + public async Task> SearchFriend(string keywords, string profileId, string cursor, int limit) + { + IList blockerIds = await relationshipClient.GetAllBlockerIds(profileId.ToString()); + IList blockeeIds = await relationshipClient.GetAllBlockeeIds(profileId.ToString()); + + var specification = new SpecificationWithCursor + { + Criteria = userProfile => + (string.IsNullOrEmpty(keywords) || + userProfile.FirstName.Contains(keywords, StringComparison.CurrentCultureIgnoreCase) || + userProfile.LastName.Contains(keywords, StringComparison.CurrentCultureIgnoreCase) || + userProfile.Username.Contains(keywords, StringComparison.CurrentCultureIgnoreCase)) + &!blockerIds.Concat(blockeeIds).Contains(userProfile.Id.ToString()), + Cursor = cursor, + Limit = limit + }; + return await userProfileRepository.GetPagedAsync(specification); + } public async Task GetByAccountId(string id) => await userProfileRepository.GetByAccountIdAsync(Guid.Parse(id)); diff --git a/Profile.Presentation/Controllers/FriendController.cs b/Profile.Presentation/Controllers/FriendController.cs index b78472a..20ff109 100644 --- a/Profile.Presentation/Controllers/FriendController.cs +++ b/Profile.Presentation/Controllers/FriendController.cs @@ -91,6 +91,60 @@ public async Task GetFriendSuggestions([FromQuery] string nextCur }); } + [EndpointDescription("Retrieve friend suggestions")] + [ProducesResponseType(typeof(CursorPagedResult), StatusCodes.Status200OK)] + [Authorize] + [HttpGet("search")] + public async Task SearchFriends([FromQuery] string nextCursor, [FromQuery] int limit = 10) + { + string currentProfileId = GetCurrentProfileId != null ? GetCurrentProfileId().ToString() + : throw new BaseException(BaseError.PROFILE_NOT_FOUND, StatusCodes.Status404NotFound); + + var suggestions = await userProfileService.GetFriendSuggestions(currentProfileId, nextCursor, limit); + + var photoMetadataIds = suggestions.Items + .Where(profile => profile.AvatarId != null) + .Select(profile => profile.AvatarId) + .Distinct(); + + var photoMetadataTasks = photoMetadataIds.Select(async id => + { + var metadata = await fileClient.GetPhotoMetadata(id.ToString()); + return new { Id = id, Metadata = metadata ??= new PhotoMetadataResponse { Id = id.Value } }; + }); + var photoMetadataDict = (await Task.WhenAll(photoMetadataTasks)).ToDictionary(x => x.Id, x => x.Metadata); + + var resultHasCount = await relationshipClient + .CountMutualFriends(currentProfileId, suggestions.Items.Select(f => f.Id.ToString()).ToList()); + + var resultHasCountDict = resultHasCount.ToDictionary(p => p.ProfileId); + + IList result = []; + foreach (var item in suggestions.Items) + { + var userProfile = mapper.Map(item); + if (userProfile.Avatar != null) + { + if (photoMetadataDict.TryGetValue(item.AvatarId, out var avatar)) + { + userProfile.Avatar = avatar; + } + } + + var itemResponse = mapper.Map(userProfile); + itemResponse.Status = "NotConnected"; + if (resultHasCountDict.TryGetValue(item.Id.ToString(), out var rs)) + if (rs.Count > 0) itemResponse.MutualFriendsCount = rs.Count; + + result.Add(itemResponse); + } + + return Ok(new CursorPagedResult() + { + Items = result, + NextCursor = suggestions.NextCursor + }); + } //[EndpointDescription("Retrieve requests")] //[ProducesResponseType(typeof(CursorPagedResult), StatusCodes.Status200OK)] //[Authorize] diff --git a/Relationship.Application/GrpcServices/GrpcRelationshipService.cs b/Relationship.Application/GrpcServices/GrpcRelationshipService.cs index 6e89f15..21a3200 100644 --- a/Relationship.Application/GrpcServices/GrpcRelationshipService.cs +++ b/Relationship.Application/GrpcServices/GrpcRelationshipService.cs @@ -123,7 +123,7 @@ public override async Task getAllBlockerIds(ProfileRequest r response.Ids.AddRange(source); return response; } - + public override async Task getAllBlockeeIds(ProfileRequest request, ServerCallContext context) { logger.LogInformation("Get blockees for ProfileId: {ProfileId}", request.Id); @@ -132,7 +132,14 @@ public override async Task getAllBlockeeIds(ProfileRequest r response.Ids.AddRange(source); return response; } - + public override async Task getFriendsOfMutualFriends(ProfileRequest request, ServerCallContext context) + { + logger.LogInformation("Get mutual friend count for ProfileId: {ProfileId}", request.Id); + var source = await friendshipService.GetAllMutualFriendsWithCount(request.Id); + var response = new MutualFriendCountResponse(); + response.ProfileIdsWithMutualCounts.AddRange(source.Select(mapper.Map)); + return response; + } public override async Task countMutualFriends(MutualFriendCountRequest request, ServerCallContext context) { logger.LogInformation("Get mutual friend count for ProfileId: {ProfileId}", request.CurrentProfileId); diff --git a/Relationship.Application/IServices/IFriendshipService.cs b/Relationship.Application/IServices/IFriendshipService.cs index b9b4550..5502291 100644 --- a/Relationship.Application/IServices/IFriendshipService.cs +++ b/Relationship.Application/IServices/IFriendshipService.cs @@ -22,6 +22,10 @@ public interface IFriendshipService public Task> GetAllPendingRequestIds(string profile); + public Task> GetAllMutualFriendIds(string profile); + + public Task> GetAllMutualFriendsWithCount(string profile); + public Task> GetAllRequestIds(string profile); public Task> GetAllSentRequestIds(string profile); diff --git a/Relationship.Application/Services/FriendshipService.cs b/Relationship.Application/Services/FriendshipService.cs index 9284b1d..3b91512 100644 --- a/Relationship.Application/Services/FriendshipService.cs +++ b/Relationship.Application/Services/FriendshipService.cs @@ -216,10 +216,11 @@ public async Task> GetAllPendingRequestIds(string profileId) public async Task> CountMutualFriends(string currentProfileId, IList profileIds) { - var list = await friendshipRepository.CountMutualFriends(profileIds, Guid.Parse(currentProfileId)); - return list.Select(item => new ProfileIdWithMutualCount + var list = profileIds.Select(x => Guid.Parse(x)).ToList(); + var friendsOfMutualFriends = await friendshipRepository.GetMutualFriendsAndCount(Guid.Parse(currentProfileId), list); + return friendsOfMutualFriends.Select(item => new ProfileIdWithMutualCount { - ProfileId = item.FriendId.ToString(), + ProfileId = item.MutualFriendId.ToString(), Count = item.MutualFriendCount }).ToList(); } @@ -271,6 +272,21 @@ private async Task PublishFriendshipNotificationCommands(Friendship entity) await messageBus.Publish(notificationCommand); } + public async Task> GetAllMutualFriendsWithCount(string profile) + { + + var list = await friendshipRepository.GetFriendsOfMutualFriend(Guid.Parse(profile)); + var friendsOfMutualFriends = await friendshipRepository.GetMutualFriendsAndCount(Guid.Parse(profile), list); + return friendsOfMutualFriends.Select(item => new ProfileIdWithMutualCount + { + ProfileId = item.MutualFriendId.ToString(), + Count = item.MutualFriendCount + }).ToList(); + } + public Task> GetAllMutualFriendIds(string profile) + { + throw new NotImplementedException(); + } } } diff --git a/Relationship.Domain/Repositories/IFriendshipRepository.cs b/Relationship.Domain/Repositories/IFriendshipRepository.cs index d0b2438..fee21d5 100644 --- a/Relationship.Domain/Repositories/IFriendshipRepository.cs +++ b/Relationship.Domain/Repositories/IFriendshipRepository.cs @@ -27,10 +27,12 @@ public interface IFriendshipRepository : ISqlRepository Task CountMutualFriends(Guid profileId, Guid currentProfile); - Task> GetAllFriendIdsOfCurrentUserAsync(Guid currentUserId); - Task> GetAllMutualFriendIdsAsync(Guid currentUserId, IList friendsOfCurrentUser); + Task> GetMutualFriendsAndCount(Guid currentUserId, IList friendsOfMutualFriend); + + Task> GetFriendsOfMutualFriend(Guid currentUserId); + Task> CountMutualFriends(IList results, Guid currentUserId); Task> GetAllRequestIdsAsync(Guid currentUserId); diff --git a/Relationship.Infrastructure/Repositories/FriendshipRepository.cs b/Relationship.Infrastructure/Repositories/FriendshipRepository.cs index f24389c..0819209 100644 --- a/Relationship.Infrastructure/Repositories/FriendshipRepository.cs +++ b/Relationship.Infrastructure/Repositories/FriendshipRepository.cs @@ -60,13 +60,48 @@ public async Task> GetAllMutualFriendIdsAsync(Guid currentUserId, IL .Distinct() .ToListAsync(); } - - public async Task> GetAllFriendIdsOfCurrentUserAsync(Guid currentUserId) + + public async Task> GetFriendsOfMutualFriend(Guid currentUserId) { - return await context.Friendships - .Where(f => (f.SenderId == currentUserId || f.ReceiverId == currentUserId) && f.Status == FriendshipStatus.Connected ) - .Select(f => f.SenderId == currentUserId ? f.ReceiverId : f.SenderId) + var friendsOfUser = await DbSet + .Where(f1 => (f1.SenderId == currentUserId || f1.ReceiverId == currentUserId) && f1.Status == FriendshipStatus.Connected) + .Select(f1 => f1.SenderId == currentUserId ? f1.ReceiverId : f1.SenderId) + .ToListAsync(); + + var mutualFriends = await DbSet + .Where(f2 => friendsOfUser.Contains(f2.SenderId) || friendsOfUser.Contains(f2.ReceiverId)) + .Where(f2 => f2.Status == FriendshipStatus.Connected && f2.SenderId != currentUserId && f2.ReceiverId != currentUserId) + .Select(f2 => friendsOfUser.Contains(f2.SenderId) ? f2.ReceiverId : f2.SenderId) + .Distinct() .ToListAsync(); + + return mutualFriends; + } + + public int GetMutualFriendCount(IList currentUserFiends, IList friendOfFriends) + { + var mutualFriends = currentUserFiends.Intersect(friendOfFriends).ToList(); + + int mutualFriendCount = mutualFriends.Count; + + return mutualFriendCount; + } + + public async Task> GetMutualFriendsAndCount(Guid currentUserId, IList friendsOfMutualFriend) + { + var friendsOfUser = await GetAllFriendIdsAsync(currentUserId); + + var friendsOfMutualFriendAndCount = new List<(Guid MutualFriendId, int MutualFriendCount)>(); + + foreach (var friend in friendsOfMutualFriend) + { + var mutualFriendFriends = await GetAllFriendIdsAsync(friend); + + var mutualFriendCount = GetMutualFriendCount(friendsOfUser, mutualFriendFriends); + + friendsOfMutualFriendAndCount.Add((MutualFriendId: friend, MutualFriendCount: mutualFriendCount)); + } + return friendsOfMutualFriendAndCount; } public async Task> GetAllPendingRequestIdsAsync(Guid currentUserId) @@ -93,22 +128,26 @@ public async Task> GetAllSentRequestIdsAsync(Guid currentUserId) .ToListAsync(); } - public async Task> CountMutualFriends(IList results, Guid currentUserId) + public async Task> CountMutualFriends(IList friendIds, Guid currentUserId) { - var resultGuids = results.Select(Guid.Parse).ToList(); - var queryResult = await context.Friendships - .Where(f => resultGuids.Contains(f.SenderId) || resultGuids.Contains(f.ReceiverId)) - .Where(f => f.SenderId != currentUserId && f.ReceiverId != currentUserId) - .Select(f => f.SenderId == currentUserId ? f.ReceiverId : f.SenderId) - .GroupBy(friendId => friendId) - .Select(group => new - { - FriendId = group.Key, - Count = group.Count() - }) - .ToListAsync(); + var mutualFriendCounts = await context.Friendships + .Where(f1 => (f1.SenderId == currentUserId || f1.ReceiverId == currentUserId) && f1.Status == FriendshipStatus.Connected) + .Join( + context.Friendships, + f1 => f1.SenderId == currentUserId ? f1.ReceiverId : f1.SenderId, + f2 => f2.SenderId == currentUserId ? f2.ReceiverId : f2.SenderId, + (f1, f2) => new { FriendA = f1.SenderId == currentUserId ? f1.ReceiverId : f1.SenderId, FriendB = f2.SenderId == currentUserId ? f2.ReceiverId : f2.SenderId } + ) + .Where(match => friendIds.Contains(match.FriendB.ToString())) + .GroupBy(match => match.FriendB) + .Select(group => new + { + FriendId = group.Key, + MutualFriendCount = group.Count() + }) + .ToListAsync(); - return queryResult.Select(x => (x.FriendId, x.Count)).ToList(); + return mutualFriendCounts.Select(x => (x.FriendId, x.MutualFriendCount)).ToList(); } } diff --git a/Relationship.Presentation/Controllers/FriendshipController.cs b/Relationship.Presentation/Controllers/FriendshipController.cs index 2fa4998..3004ea1 100644 --- a/Relationship.Presentation/Controllers/FriendshipController.cs +++ b/Relationship.Presentation/Controllers/FriendshipController.cs @@ -203,8 +203,8 @@ public async Task GetFriends([FromQuery] string nextCursor, [From var friends = await friendshipService.GetFriends(currentProfileId, nextCursor, limit); // Tập hợp toàn bộ các ID cần nạp trước - var profileIds = friends.Items.Select(item => item.SenderId.ToString()).Distinct(); - profileIds.ToList().Add(currentProfileId); + var profileIds = friends.Items.Select(item => currentProfileId.Equals(item.SenderId.ToString()) ? item.ReceiverId.ToString() : item.SenderId.ToString()).Distinct(); + //profileIds.ToList().Add(currentProfileId); // Nạp toàn bộ profiles cần thiết var profiles = await commonProfileClient.GetProfiles(profileIds.ToList()); @@ -222,14 +222,15 @@ public async Task GetFriends([FromQuery] string nextCursor, [From }); var photoMetadataDict = (await Task.WhenAll(photoMetadataTasks)).ToDictionary(x => x.Id, x => x.Metadata); - var resultHasCount = await friendshipService.CountMutualFriends(currentProfileId, friends.Items.Select(f => f.SenderId.ToString()).ToList()); + var resultHasCount = await friendshipService.CountMutualFriends(currentProfileId, friends.Items.Select(f => currentProfileId.Equals(f.SenderId.ToString()) ? f.ReceiverId.ToString() : f.SenderId.ToString()).ToList()); var resultHasCountDict = resultHasCount.ToDictionary(p => p.ProfileId); IList result = []; foreach (var item in friends.Items) { - if (profileDict.TryGetValue(item.SenderId, out var profile)) + var profileId = currentProfileId.Equals(item.SenderId.ToString()) ? item.ReceiverId : item.SenderId; + if (profileDict.TryGetValue(profileId, out var profile)) { if (profile.Avatar != null) {