test to make server

This commit is contained in:
Artemy
2026-02-08 22:30:29 +07:00
parent cc2921d257
commit 0a43e35797
56 changed files with 949 additions and 442 deletions
+102 -209
View File
@@ -1,6 +1,6 @@
using Govor.API.Common.SignalR.Helpers;
using Govor.API.Hubs.Infrastructure;
using Govor.Application.Exceptions.VerifyFriendship;
using Govor.Application.Interfaces;
using Govor.Application.Interfaces.Messages;
using Govor.Application.Interfaces.Messages.Parameters;
using Govor.Contracts.Requests.SignalR;
@@ -15,20 +15,23 @@ namespace Govor.API.Hubs;
public class ChatsHub : Hub
{
private readonly ILogger<ChatsHub> _logger;
private readonly IMessageCommandService _messageCommandService;
private readonly IUserGroupsService _userService;
private readonly IMessageCommandService _commandService;
private readonly IHubUserAccessor _userAccessor;
private readonly IChatNotificationService _notifier;
private readonly IConnectionManager _connectionManager;
public ChatsHub(
ILogger<ChatsHub> logger,
IMessageCommandService messageCommandService,
IUserGroupsService userService,
IHubUserAccessor userAccessor)
IMessageCommandService commandService,
IHubUserAccessor userAccessor,
IChatNotificationService notifier,
IConnectionManager connectionManager)
{
_logger = logger;
_messageCommandService = messageCommandService;
_userService = userService;
_commandService = commandService;
_userAccessor = userAccessor;
_notifier = notifier;
_connectionManager = connectionManager;
}
public override async Task OnConnectedAsync()
@@ -36,132 +39,58 @@ public class ChatsHub : Hub
var userId = _userAccessor.GetUserId(Context);
if (userId == Guid.Empty)
{
_logger.LogWarning("User connected with invalid UserID claim.");
Context.Abort();
Context.Abort();
return;
}
await Groups.AddToGroupAsync(Context.ConnectionId, userId.ToString());
_logger.LogInformation("User {UserId} connected with ConnectionId {ConnectionId} and added to their group",
userId, Context.ConnectionId);
var userGroups = await _userService.GetUserGroupsAsync(userId);
foreach (var group in userGroups)
{
await Groups.AddToGroupAsync(Context.ConnectionId, $"group_{group.Id}");
}
await _connectionManager.OnConnectedAsync(Context.ConnectionId, userId);
_logger.LogInformation("User {UserId} connected", userId);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
public override async Task OnDisconnectedAsync(Exception? exception)
{
var userId = _userAccessor.GetUserId(Context, true);
if (userId != Guid.Empty)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, userId.ToString());
_logger.LogInformation("User {UserId} disconnected with ConnectionId {ConnectionId} and removed from their group",
userId, Context.ConnectionId);
var userGroups = await _userService.GetUserGroupsAsync(userId);
foreach (var group in userGroups)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"group_{group.Id}");
}
}
else if (exception != null)
{
_logger.LogWarning(exception,
"User disconnected with an exception and invalid UserID claim. ConnectionId: {ConnectionId}",
Context.ConnectionId);
}
else
{
_logger.LogInformation(
"User disconnected with no exception and invalid UserID claim. ConnectionId: {ConnectionId}",
Context.ConnectionId);
}
await _connectionManager.OnDisconnectedAsync(Context.ConnectionId, userId);
if (exception != null)
_logger.LogWarning(exception, "User {UserId} disconnected with error", userId);
else
_logger.LogInformation("User {UserId} disconnected", userId);
await base.OnDisconnectedAsync(exception);
}
// --- SEND ---
public async Task<HubResult<UserMessageResponse>> Send(MessageRequest request)
{
var senderId = _userAccessor.GetUserId(Context);
if (string.IsNullOrWhiteSpace(request.EncryptedContent) &&
(request.MediaAttachments == null || !request.MediaAttachments.Any()))
return await SafeExecute(async (userId) =>
{
_logger.LogWarning("Empty message received from user {UserId}", senderId);
return HubResult<UserMessageResponse>.BadRequest("Message must contain content or media.");
}
ValidateMessageRequest(request);
if (request.EncryptedContent.Length > 50_000)
{
_logger.LogWarning("User {SenderId} tried to send a too long message (length: {Length})", senderId, request.EncryptedContent.Length);
return HubResult<UserMessageResponse>.BadRequest("Message cannot exceed 50,000 characters.");
}
var sendParams = MapToSendMessage(request, userId);
var result = await _commandService.SendMessageAsync(sendParams);
_logger.LogInformation("Sending message from {SenderId} to {RecipientId} ({RecipientType})",
senderId, request.RecipientId, request.RecipientType);
if (!result.IsSuccess)
throw new InvalidOperationException(result.Exception.Message ?? "Failed to send message");
var sendMessageParams = new SendMessage(
EncryptContent: request.EncryptedContent,
ReplyToMessageId: request.ReplyToMessageId,
FromUserId: senderId,
RecipientId: request.RecipientId,
RecipientType: request.RecipientType,
SendAt: DateTime.UtcNow,
Media: request.MediaAttachments?.Select(f => new SendMedia(f.MediaId, f.EncryptedKey)) ??
Array.Empty<SendMedia>()
);
var response = MapToResponse(result.Message, request.ReplyToMessageId);
await _notifier.NotifyMessageSentAsync(response);
try
{
var result = await _messageCommandService.SendMessageAsync(sendMessageParams);
if (!result.IsSuccess || result.Message.Id == Guid.Empty)
return LogAndError<UserMessageResponse>(senderId, request.RecipientId, "Failed to send message", result.Exception);
var response = BuildUserMessageResponse(result.Message, request.ReplyToMessageId);
await NotifyClientsAboutMessage(response);
return HubResult<UserMessageResponse>.Ok(response);
}
catch (UnauthorizedAccessException ex)
{
return LogAndUnauthorized<UserMessageResponse>(ex, "Unauthorized sending attempt", senderId,
request.RecipientId);
}
catch (FriendshipException)
{
return HubResult<UserMessageResponse>.Unauthorized(
"You cannot send this message because you are not friends.");
}
catch (ArgumentException)
{
return HubResult<UserMessageResponse>.BadRequest("Invalid message content.");
}
catch (Exception ex)
{
return LogAndError<UserMessageResponse>(senderId, request.RecipientId, "Unhandled exception", ex);
}
}
return HubResult<UserMessageResponse>.Ok(response);
}, request.RecipientId);
}
// --- REMOVE ---
public async Task<HubResult<MessageRemovedResponse>> Remove(RemoveMessageRequest request)
{
var removerId = _userAccessor.GetUserId(Context);
_logger.LogInformation("Removing message {MessageId} by user {RemoverId}", request.MessageId, removerId);
try
return await SafeExecute(async (userId) =>
{
var result = await _messageCommandService.DeleteMessageAsync(new DeleteMessage(removerId, request.MessageId));
var result = await _commandService.DeleteMessageAsync(new DeleteMessage(userId, request.MessageId));
if (!result.IsSuccess || result.OriginalMessage == null)
return LogAndError<MessageRemovedResponse>(removerId, request.MessageId, "Message deletion failed", result.Exception);
throw new InvalidOperationException("Message deletion failed");
var notification = new MessageRemovedResponse
{
@@ -171,76 +100,98 @@ public class ChatsHub : Hub
RecipientType = result.OriginalMessage.RecipientType
};
await NotifyClientsAboutRemoval(notification);
await _notifier.NotifyMessageRemovedAsync(notification);
_logger.LogInformation("Message {MessageId} removed successfully by {RemoverId}", request.MessageId, removerId);
return HubResult<MessageRemovedResponse>.Ok(notification);
}
catch (UnauthorizedAccessException ex)
{
return LogAndUnauthorized<MessageRemovedResponse>(ex, "Unauthorized removal", removerId, request.MessageId);
}
catch (KeyNotFoundException ex)
{
return LogAndNotFound<MessageRemovedResponse>(ex, "Message not found", removerId, request.MessageId);
}
catch (Exception ex)
{
return LogAndError<MessageRemovedResponse>(removerId, request.MessageId, "Unhandled deletion error", ex);
}
}, request.MessageId);
}
// --- EDIT ---
public async Task<HubResult<MessageEditResponse>> Edit(EditMessageRequest request)
{
var editor = _userAccessor.GetUserId(Context);
_logger.LogInformation("Editing message {MessageId} by user {EditorId}", request.MessageId, editor);
var editMessageParam = new EditMessage(editor,
request.MessageId,
request.NewEncryptedContent,
DateTime.UtcNow);
try
return await SafeExecute(async (userId) =>
{
var result = await _messageCommandService.EditMessageAsync(editMessageParam);
var editParams = new EditMessage(userId, request.MessageId, request.NewEncryptedContent, DateTime.UtcNow);
var result = await _commandService.EditMessageAsync(editParams);
if (!result.IsSuccess || result.OriginalMessage == null)
return LogAndError<MessageEditResponse>(editor, request.MessageId, "Edit message error",
result.Exception);
throw new InvalidOperationException("Edit message error");
var response = new MessageEditResponse()
var response = new MessageEditResponse
{
MessageId = result.messageId,
EditorId = editor,
EditorId = userId,
RecipientId = result.OriginalMessage.RecipientId,
RecipientType = result.OriginalMessage.RecipientType,
NewEncryptedContent = request.NewEncryptedContent,
EditedAt = editMessageParam.EditedAt,
EditedAt = editParams.EditedAt,
};
await NotifyClientsAboutEdit(response);
_logger.LogInformation("Message {MessageId} edited successfully by {editor}", request.MessageId, editor);
await _notifier.NotifyMessageEditedAsync(response);
return HubResult<MessageEditResponse>.Ok(response);
}, request.MessageId);
}
private async Task<HubResult<T>> SafeExecute<T>(Func<Guid, Task<HubResult<T>>> action, Guid targetIdForLog)
{
var userId = _userAccessor.GetUserId(Context);
try
{
return await action(userId);
}
catch (UnauthorizedAccessException ex)
{
return LogAndUnauthorized<MessageEditResponse>(ex, "Unauthorized editing", editor, request.MessageId);
_logger.LogWarning(ex, "Unauthorized: {UserId} -> {TargetId}", userId, targetIdForLog);
return HubResult<T>.Unauthorized("You are not authorized.");
}
catch (KeyNotFoundException ex)
catch (FriendshipException)
{
return LogAndNotFound<MessageEditResponse>(ex, "Message not found", editor, request.MessageId);
return HubResult<T>.Unauthorized("You cannot perform this action due to friendship status.");
}
catch (ArgumentException ex)
{
return HubResult<T>.BadRequest(ex.Message);
}
catch (KeyNotFoundException)
{
return HubResult<T>.NotFound("Resource not found.");
}
catch (Exception ex)
{
return LogAndError<MessageEditResponse>(editor, request.MessageId, "Unhandled exception error", ex);
_logger.LogError(ex, "Error executing hub method for {UserId}", userId);
return HubResult<T>.Error("Internal server error");
}
}
private void ValidateMessageRequest(MessageRequest request)
{
if (string.IsNullOrWhiteSpace(request.EncryptedContent) &&
(request.MediaAttachments == null || !request.MediaAttachments.Any()))
{
throw new ArgumentException("Message must contain content or media.");
}
if (request.EncryptedContent.Length > 50_000)
{
throw new ArgumentException("Message is too long.");
}
}
private SendMessage MapToSendMessage(MessageRequest request, Guid senderId)
{
return new SendMessage(
EncryptContent: request.EncryptedContent,
ReplyToMessageId: request.ReplyToMessageId,
FromUserId: senderId,
RecipientId: request.RecipientId,
RecipientType: request.RecipientType,
SendAt: DateTime.UtcNow,
Media: request.MediaAttachments?.Select(f => new SendMedia(f.MediaId, f.EncryptedKey))
?? Array.Empty<SendMedia>()
);
}
#region common
private UserMessageResponse BuildUserMessageResponse(Message message, Guid? replyToId)
private UserMessageResponse MapToResponse(Message message, Guid? replyToId)
{
return new UserMessageResponse
{
@@ -255,62 +206,4 @@ public class ChatsHub : Hub
ReplyToMessageId = replyToId
};
}
private async Task NotifyClientsAboutMessage(UserMessageResponse response)
{
string group = response.RecipientType == RecipientType.User
? response.RecipientId.ToString()
: $"group_{response.RecipientId}";
await Clients.Group(group).SendAsync("ReceiveMessage", response);
await Clients.Caller.SendAsync("MessageSent", response);
}
private async Task NotifyClientsAboutRemoval(MessageRemovedResponse response)
{
if (response.RecipientType == RecipientType.User)
{
await Clients.Group(response.SenderId.ToString()).SendAsync("MessageRemoved", response);
if (response.SenderId != response.RecipientId)
await Clients.Group(response.RecipientId.ToString()).SendAsync("MessageRemoved", response);
}
else
{
await Clients.Group($"group_{response.RecipientId}").SendAsync("MessageRemoved", response);
}
}
private async Task NotifyClientsAboutEdit(MessageEditResponse response)
{
if (response.RecipientType == RecipientType.User)
{
await Clients.Group(response.EditorId.ToString()).SendAsync("MessageEdit", response);
if (response.EditorId != response.RecipientId)
await Clients.Group(response.RecipientId.ToString()).SendAsync("MessageEdit", response);
}
else
{
await Clients.Group($"group_{response.RecipientId}").SendAsync("MessageEdit", response);
}
}
// Logging helpers
private HubResult<T> LogAndError<T>(Guid userId, Guid targetId, string message, Exception ex)
{
_logger.LogError(ex, "{Message} from {UserId} to {TargetId}", message, userId, targetId);
return HubResult<T>.Error(ex?.Message ?? "Internal server error");
}
private HubResult<T> LogAndUnauthorized<T>(Exception ex, string msg, Guid userId, Guid targetId)
{
_logger.LogWarning(ex, "{Msg}: {UserId} -> {TargetId}", msg, userId, targetId);
return HubResult<T>.Unauthorized("You are not authorized to perform this action.");
}
private HubResult<T> LogAndNotFound<T>(Exception ex, string msg, Guid userId, Guid targetId)
{
_logger.LogWarning(ex, "{Msg}: {UserId} -> {TargetId}", msg, userId, targetId);
return HubResult<T>.NotFound("Message not found.");
}
#endregion
}
+10 -4
View File
@@ -44,6 +44,7 @@ public class FriendsHub : Hub
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
var userId = _userAccessor.GetUserId(Context, true);
@@ -76,10 +77,14 @@ public class FriendsHub : Hub
{
var userId = _userAccessor.GetUserId(Context);
var friendship = await _friendRequestService.SendAsync(userId, targetUserId);
var dto = _mapper.Map<FriendshipDto>(friendship);
await Clients.Group(targetUserId.ToString())
.SendAsync("FriendRequestReceived", _mapper.Map<FriendshipDto>(friendship));
.SendAsync("FriendRequestReceived", dto);
await Clients.Group(userId.ToString())
.SendAsync("YourFriendRequestReceived", dto);
_logger.LogInformation($"Friend request received for user {targetUserId} from {userId}.");
return HubResult<object>.Created();
}
@@ -111,12 +116,13 @@ public class FriendsHub : Hub
{
var userId = _userAccessor.GetUserId(Context);
var friendship = await _friendRequestService.AcceptAsync(friendshipId, userId);
var dto = _mapper.Map<FriendshipDto>(friendship);
await Clients.Group(userId.ToString())
.SendAsync("FriendRequestAccepted", _mapper.Map<FriendshipDto>(friendship));
.SendAsync("FriendRequestAccepted", dto);
await Clients.Group(friendship.RequesterId.ToString())
.SendAsync( "YourFriendRequestAccepted", _mapper.Map<UserDto>(friendship.Addressee));
.SendAsync( "YourFriendRequestAccepted", dto);
_logger.LogInformation($"Friend request accepted for user {userId} from {userId}.");
return HubResult<object>.Ok();
@@ -0,0 +1,13 @@
namespace Govor.API.Hubs.Infrastructure;
public static class ChatHubConstants
{
public const string ReceiveMessage = "ReceiveMessage";
public const string MessageSent = "MessageSent";
public const string MessageRemoved = "MessageRemoved";
public const string MessageEdited = "MessageEdit";
public static string GetUserGroup(Guid userId) => userId.ToString();
public static string GetChatGroup(Guid groupId) => $"group_{groupId}";
public static string GetPrivateChat(Guid groupId) => $"private_{groupId}";
}
@@ -0,0 +1,73 @@
using Govor.Contracts.Responses.SignalR;
using Govor.Core.Models.Messages;
using Microsoft.AspNetCore.SignalR;
namespace Govor.API.Hubs.Infrastructure;
public class ChatNotificationService : IChatNotificationService
{
private readonly IHubContext<ChatsHub> _hubContext;
public ChatNotificationService(IHubContext<ChatsHub> hubContext)
{
_hubContext = hubContext;
}
public async Task NotifyMessageSentAsync(UserMessageResponse message)
{
if (message.RecipientType == RecipientType.User)
{
await _hubContext.Clients.Group(ChatHubConstants.GetPrivateChat(message.RecipientId))
.SendAsync(ChatHubConstants.ReceiveMessage, message);
}
else
{
await _hubContext.Clients.Group(ChatHubConstants.GetChatGroup(message.RecipientId))
.SendAsync(ChatHubConstants.ReceiveMessage, message);
}
// await _hubContext.Clients.Group(ChatHubConstants.GetUserGroup(message.SenderId))
// .SendAsync(ChatHubConstants.MessageSent, message);
}
public async Task NotifyMessageRemovedAsync(MessageRemovedResponse response)
{
await NotifyParticipantsAsync(
response.SenderId,
response.RecipientId,
response.RecipientType,
ChatHubConstants.MessageRemoved,
response);
}
public async Task NotifyMessageEditedAsync(MessageEditResponse response)
{
await NotifyParticipantsAsync(
response.EditorId,
response.RecipientId,
response.RecipientType,
ChatHubConstants.MessageEdited,
response);
}
private async Task NotifyParticipantsAsync(Guid initiatorId, Guid targetId, RecipientType type, string method, object payload)
{
if (type == RecipientType.User)
{
await _hubContext.Clients.Group(ChatHubConstants.GetUserGroup(initiatorId))
.SendAsync(method, payload);
if (initiatorId != targetId)
{
await _hubContext.Clients.Group(ChatHubConstants.GetUserGroup(targetId))
.SendAsync(method, payload);
}
}
else
{
// to groups
await _hubContext.Clients.Group(ChatHubConstants.GetChatGroup(targetId))
.SendAsync(method, payload);
}
}
}
@@ -0,0 +1,46 @@
using Govor.Application.Interfaces;
using Microsoft.AspNetCore.SignalR;
namespace Govor.API.Hubs.Infrastructure;
public class ConnectionManager : IConnectionManager
{
private readonly IUserGroupsGetterService _userGroupsGetterService;
private readonly IUserPrivateChatsGetterService _userPrivateChatsGetterService;
private readonly IHubContext<ChatsHub> _hubContext;
public ConnectionManager(IUserGroupsGetterService userGroupsGetterService, IUserPrivateChatsGetterService userPrivateChatsGetterService, IHubContext<ChatsHub> hubContext)
{
_userGroupsGetterService = userGroupsGetterService;
_userPrivateChatsGetterService = userPrivateChatsGetterService;
_hubContext = hubContext;
}
public async Task OnConnectedAsync(string connectionId, Guid userId)
{
// user
await _hubContext.Groups.AddToGroupAsync(connectionId, ChatHubConstants.GetUserGroup(userId));
// groups
var userGroups = await _userGroupsGetterService.GetUserGroupsAsync(userId);
foreach (var group in userGroups)
{
await _hubContext.Groups.AddToGroupAsync(connectionId, ChatHubConstants.GetChatGroup(group.Id));
}
// private chats
var chats = await _userPrivateChatsGetterService.GetUserChatsAsync(userId);
foreach (var group in chats)
{
await _hubContext.Groups.AddToGroupAsync(connectionId, ChatHubConstants.GetPrivateChat(group.Id));
}
}
public async Task OnDisconnectedAsync(string connectionId, Guid userId)
{
if (userId != Guid.Empty)
{
await _hubContext.Groups.RemoveFromGroupAsync(connectionId, ChatHubConstants.GetUserGroup(userId));
}
}
}
@@ -0,0 +1,10 @@
using Govor.Contracts.Responses.SignalR;
namespace Govor.API.Hubs.Infrastructure;
public interface IChatNotificationService
{
Task NotifyMessageSentAsync(UserMessageResponse message);
Task NotifyMessageRemovedAsync(MessageRemovedResponse response);
Task NotifyMessageEditedAsync(MessageEditResponse response);
}
@@ -0,0 +1,7 @@
namespace Govor.API.Hubs.Infrastructure;
public interface IConnectionManager
{
Task OnConnectedAsync(string connectionId, Guid userId);
Task OnDisconnectedAsync(string connectionId, Guid userId);
}
+36 -18
View File
@@ -34,21 +34,22 @@ public class PresenceHub : Hub
public override async Task OnConnectedAsync()
{
var userId = _userAccessor.GetUserId(Context);
if (userId == Guid.Empty || await _users.ExistsByIdAsync(userId) == false)
if (userId == Guid.Empty)
{
_logger.LogWarning("User connected with invalid UserId claim.");
Context.Abort();
return;
}
var isFirstConnection = _onlineUserStore.AddConnection(userId, Context.ConnectionId);
_onlineUserStore.SetOnlineUser(userId);
await Groups.AddToGroupAsync(Context.ConnectionId, userId.ToString());
var friends = await _scopeService.GetNotifiedUsers(userId);
foreach (var recipient in friends)
if (isFirstConnection)
{
await Clients.Group(recipient.ToString())
var friends = await _scopeService.GetNotifiedUsers(userId);
await Clients.Groups(friends.Select(f => f.ToString()).ToList())
.SendAsync("UserOnline", userId);
}
@@ -74,21 +75,38 @@ public class PresenceHub : Hub
return;
}
_onlineUserStore.SetOfflineUser(userId);
var isLastConnection = _onlineUserStore.RemoveConnection(userId, Context.ConnectionId);
// Updating was online
var user = await _users.FindByIdAsync(userId);
user.WasOnline = DateTime.UtcNow;
await _users.UpdateAsync(user);
var friends = await _scopeService.GetNotifiedUsers(userId);
foreach (var recipient in friends)
if (isLastConnection)
{
await Clients.Group(recipient.ToString())
.SendAsync("UserOffline", userId);
_ = Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(5));
var currentConnections = _onlineUserStore.GetConnections(userId);
if (currentConnections == null || !currentConnections.Any())
{
var friends = await _scopeService.GetNotifiedUsers(userId);
await Clients.Groups(friends.Select(f => f.ToString()).ToList())
.SendAsync("UserOffline", userId);
try
{
var user = await _users.FindByIdAsync(userId);
if (user != null)
{
user.WasOnline = DateTime.UtcNow;
await _users.UpdateAsync(user);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating WasOnline for {UserId}", userId);
}
}
});
}
await base.OnDisconnectedAsync(exception);
}
}
+82 -106
View File
@@ -1,11 +1,9 @@
using Govor.API.Common.SignalR.Helpers;
using Govor.Application.Interfaces;
using Govor.Application.Interfaces.Friends;
using Govor.Application.Interfaces.Infrastructure.Extensions;
using Govor.Application.Interfaces.Medias;
using Govor.Contracts.DTOs;
using Govor.Contracts.Responses.SignalR;
using Govor.Core.Models;
using Govor.Core.Models.Users;
using Govor.Core.Repositories.Groups;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
@@ -19,6 +17,7 @@ public class ProfileHub : Hub
private readonly IFriendshipService _friendsService;
private readonly IProfileService _profileService;
private readonly IHubUserAccessor _userAccessor;
private readonly ISynchingService _synchingService;
private readonly ILogger<ProfileHub> _logger;
private readonly IMediaService _mediaService;
@@ -27,46 +26,24 @@ public class ProfileHub : Hub
IFriendshipService friendsService,
IProfileService profileService,
IHubUserAccessor userAccessor,
ISynchingService synchingService,
IMediaService mediaService,
ILogger<ProfileHub> logger)
{
_groupsRepository = groupsRepository;
_friendsService = friendsService;
_profileService = profileService;
_userAccessor = userAccessor;
_synchingService = synchingService;
_mediaService = mediaService;
_logger = logger;
}
public override async Task OnConnectedAsync()
{
var userId = _userAccessor.GetUserId(Context);
await Groups.AddToGroupAsync(Context.ConnectionId, $"user-{userId}");
// Friends
try
{
var friendships = await _friendsService.GetFriendsAsync(userId);
foreach (var friends in friendships)
await Groups.AddToGroupAsync(Context.ConnectionId, $"friends-{friends.Id}");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to get friends for user {userId}", userId);
}
// Groups
try
{
var groups = await _groupsRepository.GetByUserIdAsync(userId);
foreach (var group in groups)
await Groups.AddToGroupAsync(Context.ConnectionId, $"group-{group.Id}");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to get groups for user {userId}", userId);
}
_logger.LogInformation("User {UserId} connected with ConnectionId {ConnectionId}", userId, Context.ConnectionId);
await Groups.AddToGroupAsync(Context.ConnectionId, $"user:{userId}");
await base.OnConnectedAsync();
}
@@ -76,57 +53,36 @@ public class ProfileHub : Hub
var userId = _userAccessor.GetUserId(Context);
await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"user-{userId}");
// Friends
try
{
var friendships = await _friendsService.GetFriendsAsync(userId);
foreach (var friendship in friendships)
await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"friends-{friendship.Id}");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to remove user {userId} from friend groups", userId);
}
// Groups
try
{
var groups = await _groupsRepository.GetByUserIdAsync(userId);
foreach (var group in groups)
await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"group-{group.Id}");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to remove user {userId} from groups", userId);
}
await base.OnDisconnectedAsync(exception);
}
public async Task<HubResult<bool>> SetDescription(string description)
{
if(description.Length > 500)
return HubResult<bool>.Error("The description cannot be longer than 500 characters.");
if (description.Length > 500)
return HubResult<bool>.Error("Description length exceeded.");
var userId = _userAccessor.GetUserId(Context);
try
{
description = _synchingService.NormalizeNewlines(description);
await _profileService.SetDescription(description, userId);
var payload = new { userId, description };
var payload = new DescriptionUpdatePayload
{
UserId = userId,
Description = description
};
await NotifyFriendsAndGroupsAsync(userId, "DescriptionUpdated", payload);
await NotifyProfileUpdatedAsync(userId, "DescriptionUpdated", payload);
return HubResult<bool>.Ok(true);
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while updating the user's {userId} descripton!", userId);
return HubResult<bool>.Error("An unaccounted error on the server!");
_logger.LogError(ex, "Failed to update description for user {UserId}", userId);
return HubResult<bool>.Error("Server error.");
}
}
@@ -136,14 +92,14 @@ public class ProfileHub : Hub
try
{
if (iconId == Guid.Empty)
return HubResult<bool>.BadRequest("IconId can't be empty!");
if (iconId == Guid.Empty || !await _mediaService.HasMediaAsync(iconId))
return HubResult<bool>.BadRequest("Invalid icon id.");
await _profileService.SetNewIcon(userId, iconId);
var payload = new { userId, iconId };
var payload = new AvatarUpdatePayload(){UserId = userId, IconId = iconId};
await NotifyFriendsAndGroupsAsync(userId, "AvatarUpdated", payload);
await NotifyProfileUpdatedAsync(userId, "AvatarUpdated", payload);
return HubResult<bool>.Ok(true);
}
@@ -154,50 +110,70 @@ public class ProfileHub : Hub
}
}
private async Task NotifyFriendsAndGroupsAsync(Guid userId, string eventName, object payload)
private async Task NotifyProfileUpdatedAsync(
Guid UserId,
string eventName,
object payload)
{
var friendIds = Enumerable.Empty<User>();
var groups = Enumerable.Empty<ChatGroup>();
try
{
friendIds = await _friendsService.GetFriendsAsync(userId);
var recipients = new HashSet<Guid>();
// 1. Friends
try
{
var friends = await _friendsService.GetFriendsAsync(UserId);
recipients.UnionWith(friends.Select(f => f.Id));
}
catch (Exception ex)
{
_logger.LogWarning(ex, "User has no friends for user {userId}", UserId);
}
// 2. (pending)
try
{
var potentialFriends = await _friendsService.GetPotentialFriendsAsync(UserId);
recipients.UnionWith(potentialFriends.Select(p => p.Id));
}
catch (Exception ex)
{
_logger.LogWarning(ex, "User has potential friends for user {userId}", UserId);
}
// 3. groups
/*var groups = await _groupsRepository.GetByUserIdAsync(authorUserId);
foreach (var group in groups)
{
var members =
await _groupsRepository.Get(group.Id);
recipients.UnionWith(members.Select(m => m.Id));
}*/
recipients.Add(UserId);
// 5.
// recipients.RemoveWhere(id =>
// !_profileVisibilityService.CanViewProfile(id, authorUserId));
// 6. 1 user = 1 event
var recipientsStrings =
recipients.Select(r => $"user:{r}").ToList();
foreach (var recipientId in recipients)
{
await Clients.Groups(recipientsStrings)
.SendAsync(eventName, payload);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to load friend list for notifications. userId: {userId}", userId);
_logger.LogError(ex,
"Failed to notify profile update for user {UserId}",
UserId);
}
try
{
groups = await _groupsRepository.GetByUserIdAsync(userId);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to load group list for notifications. userId: {userId}", userId);
}
var groupIds = groups.Select(g => g.Id).ToList();
// Current user
await Clients.Group($"user-{userId}")
.SendAsync(eventName, payload);
// Friends
foreach (var friend in friendIds)
{
await Clients.Group($"friends-{friend.Id}")
.SendAsync(eventName, payload);
}
// Groups
foreach (var groupId in groupIds)
{
await Clients.Group($"group-{groupId}")
.SendAsync(eventName, payload);
}
_logger.LogInformation("Sent {EventName} for {UserId} to {Friends} friends and {Groups} groups",
eventName, userId, friendIds.Count(), groupIds.Count);
}
}