mirror of
https://github.com/Govor-team/Govor.git
synced 2026-07-22 20:24:55 +00:00
test to make server
This commit is contained in:
+102
-209
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user