mirror of
https://github.com/Govor-team/Govor.git
synced 2026-07-21 11:44:56 +00:00
Refactor user ID access in SignalR hubs and add online user tracking
Introduces IHubUserAccessor and its implementation to centralize user ID retrieval from SignalR HubCallerContext, replacing duplicated logic in ChatsHub, FriendsHub, and PresenceHub. Moves extension and mapping files to a Common directory, adds UserToUserDtoMappingAction for online status mapping, and implements OnlineUserStore with tests for tracking online users. Updates dependency injection and test code to use the new abstractions.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
using Govor.Application.Services.Authentication;
|
||||
|
||||
namespace Govor.API.Extensions;
|
||||
namespace Govor.API.Common.Extensions;
|
||||
|
||||
public static class AddOptionExtensions
|
||||
{
|
||||
+5
-1
@@ -1,3 +1,5 @@
|
||||
using Govor.API.Common.Mapping;
|
||||
using Govor.API.Common.SignalR.Helpers;
|
||||
using Govor.Application.Infrastructure.AdminsStuff;
|
||||
using Govor.Application.Infrastructure.Extensions;
|
||||
using Govor.Application.Infrastructure.Validators;
|
||||
@@ -34,7 +36,7 @@ using Govor.Data;
|
||||
using Govor.Data.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Govor.API.Extensions;
|
||||
namespace Govor.API.Common.Extensions;
|
||||
|
||||
public static class ConfigurationProgramExtensions
|
||||
{
|
||||
@@ -83,6 +85,8 @@ public static class ConfigurationProgramExtensions
|
||||
|
||||
// Auto Mapper
|
||||
services.AddAutoMapper(typeof(MappingProfile));
|
||||
|
||||
services.AddScoped<IHubUserAccessor, HubUserAccessor>();
|
||||
}
|
||||
|
||||
public static void AddRepositories(this IServiceCollection services)
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using Govor.API.Filters;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Govor.API.Extensions;
|
||||
namespace Govor.API.Common.Extensions;
|
||||
|
||||
public static class ConfigurationSignalR
|
||||
{
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using Serilog;
|
||||
|
||||
namespace Govor.API.Extensions;
|
||||
namespace Govor.API.Common.Extensions;
|
||||
|
||||
public static class ConfiguratorLoggerExtensions
|
||||
{
|
||||
@@ -1,11 +1,12 @@
|
||||
using AutoMapper;
|
||||
using Govor.API.Extensions.Mapping;
|
||||
using Govor.Contracts.DTOs;
|
||||
using Govor.Contracts.Responses;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Messages;
|
||||
using Govor.Core.Models.Users;
|
||||
|
||||
namespace Govor.API.Extensions;
|
||||
namespace Govor.API.Common.Mapping;
|
||||
|
||||
public class MappingProfile : Profile
|
||||
{
|
||||
@@ -16,7 +17,9 @@ public class MappingProfile : Profile
|
||||
CreateMap<MessageReaction, MessageReactionResponse>();
|
||||
CreateMap<MessageView, MessageViewResponse>();
|
||||
|
||||
CreateMap<User, UserDto>();
|
||||
CreateMap<User, UserDto>()
|
||||
.AfterMap<UserToUserDtoMappingAction>();
|
||||
|
||||
CreateMap<Friendship, FriendshipDto>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using AutoMapper;
|
||||
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||
using Govor.Contracts.DTOs;
|
||||
using Govor.Core.Models.Users;
|
||||
|
||||
namespace Govor.API.Extensions.Mapping;
|
||||
|
||||
public class UserToUserDtoMappingAction : IMappingAction<User, UserDto>
|
||||
{
|
||||
private readonly IOnlineUserStore _onlineUserStore;
|
||||
|
||||
public UserToUserDtoMappingAction(IOnlineUserStore onlineUserStore)
|
||||
{
|
||||
_onlineUserStore = onlineUserStore;
|
||||
}
|
||||
|
||||
public void Process(User source, UserDto destination, ResolutionContext context)
|
||||
{
|
||||
destination.IsOnline = _onlineUserStore.IsOnline(source.Id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Govor.API.Common.SignalR.Helpers;
|
||||
|
||||
public class HubUserAccessor : IHubUserAccessor
|
||||
{
|
||||
private readonly ILogger<HubUserAccessor> _logger;
|
||||
|
||||
public HubUserAccessor(ILogger<HubUserAccessor> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Guid GetUserId(HubCallerContext context, bool suppressException = false)
|
||||
{
|
||||
var userIdClaim = context.User?.FindFirst("userId")?.Value;
|
||||
if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
|
||||
{
|
||||
if (!suppressException)
|
||||
{
|
||||
_logger.LogError("Could not retrieve sender userId. Claim was: {UserIDClaim}", userIdClaim);
|
||||
throw new UnauthorizedAccessException("userID claim is missing or invalid.");
|
||||
}
|
||||
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Govor.API.Common.SignalR.Helpers;
|
||||
|
||||
public interface IHubUserAccessor
|
||||
{
|
||||
Guid GetUserId(HubCallerContext context, bool suppressException = false);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Govor.API.Common.SignalR.Helpers;
|
||||
using Govor.Application.Exceptions.VerifyFriendship;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.Messages;
|
||||
@@ -16,17 +17,19 @@ public class ChatsHub : Hub
|
||||
private readonly ILogger<ChatsHub> _logger;
|
||||
private readonly IMessageCommandService _messageCommandService;
|
||||
private readonly IUserGroupsService _userService;
|
||||
private readonly IHubUserAccessor _userAccessor;
|
||||
|
||||
public ChatsHub(ILogger<ChatsHub> logger, IMessageCommandService messageCommandService, IUserGroupsService userService)
|
||||
public ChatsHub(ILogger<ChatsHub> logger, IMessageCommandService messageCommandService, IUserGroupsService userService, IHubUserAccessor userAccessor)
|
||||
{
|
||||
_logger = logger;
|
||||
_messageCommandService = messageCommandService;
|
||||
_userService = userService;
|
||||
_userAccessor = userAccessor;
|
||||
}
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
var userId = GetUserId();
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
if (userId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("User connected with invalid UserID claim.");
|
||||
@@ -50,8 +53,7 @@ public class ChatsHub : Hub
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
var userId =
|
||||
GetUserId(suppressException: true);
|
||||
var userId = _userAccessor.GetUserId(Context, true);
|
||||
if (userId != Guid.Empty)
|
||||
{
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, userId.ToString());
|
||||
@@ -85,7 +87,7 @@ public class ChatsHub : Hub
|
||||
|
||||
public async Task<HubResult<UserMessageResponse>> Send(MessageRequest request)
|
||||
{
|
||||
var senderId = GetUserId();
|
||||
var senderId= _userAccessor.GetUserId(Context);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.EncryptedContent) &&
|
||||
(request.MediaAttachments == null || !request.MediaAttachments.Any()))
|
||||
@@ -144,7 +146,7 @@ public class ChatsHub : Hub
|
||||
|
||||
public async Task<HubResult<MessageRemovedResponse>> Remove(RemoveMessageRequest request)
|
||||
{
|
||||
var removerId = GetUserId();
|
||||
var removerId = _userAccessor.GetUserId(Context);
|
||||
_logger.LogInformation("Removing message {MessageId} by user {RemoverId}", request.MessageId, removerId);
|
||||
|
||||
try
|
||||
@@ -184,7 +186,7 @@ public class ChatsHub : Hub
|
||||
|
||||
public async Task<HubResult<MessageEditResponse>> Edit(EditMessageRequest request)
|
||||
{
|
||||
var editor = GetUserId();
|
||||
var editor = _userAccessor.GetUserId(Context);
|
||||
_logger.LogInformation("Editing message {MessageId} by user {EditorId}", request.MessageId, editor);
|
||||
|
||||
var editMessageParam = new EditMessage(editor,
|
||||
@@ -303,22 +305,4 @@ public class ChatsHub : Hub
|
||||
_logger.LogWarning(ex, "{Msg}: {UserId} -> {TargetId}", msg, userId, targetId);
|
||||
return HubResult<T>.NotFound("Message not found.");
|
||||
}
|
||||
|
||||
|
||||
private Guid GetUserId(bool suppressException = false)
|
||||
{
|
||||
var userIdClaim = Context.User?.FindFirst("userId")?.Value;
|
||||
if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
|
||||
{
|
||||
if (!suppressException)
|
||||
{
|
||||
_logger.LogError("Could not retrieve sender userId. Claim was: {UserIDClaim}", userIdClaim);
|
||||
throw new UnauthorizedAccessException("userID claim is missing or invalid.");
|
||||
}
|
||||
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using AutoMapper;
|
||||
using Govor.API.Common.SignalR.Helpers;
|
||||
using Govor.Application.Exceptions.FriendsService;
|
||||
using Govor.Application.Interfaces.Friends;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
@@ -13,23 +14,24 @@ public class FriendsHub : Hub
|
||||
{
|
||||
private readonly ILogger<FriendsHub> _logger;
|
||||
private readonly IFriendRequestCommandService _friendRequestService;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IHubUserAccessor _userAccessor;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public FriendsHub(IFriendRequestCommandService friendRequestService,
|
||||
ICurrentUserService currentUserService,
|
||||
|
||||
public FriendsHub(
|
||||
ILogger<FriendsHub> logger,
|
||||
IFriendRequestCommandService friendRequestService,
|
||||
IHubUserAccessor userAccessor,
|
||||
IMapper mapper)
|
||||
{
|
||||
_friendRequestService = friendRequestService;
|
||||
_currentUserService = currentUserService;
|
||||
_logger = logger;
|
||||
_friendRequestService = friendRequestService;
|
||||
_userAccessor = userAccessor;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
var userId = GetUserId();
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
if (userId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("User connected with invalid UserID claim.");
|
||||
@@ -46,8 +48,7 @@ public class FriendsHub : Hub
|
||||
}
|
||||
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
var userId =
|
||||
GetUserId(suppressException: true);
|
||||
var userId = _userAccessor.GetUserId(Context, true);
|
||||
if (userId != Guid.Empty)
|
||||
{
|
||||
// Remove user from their own group
|
||||
@@ -76,7 +77,7 @@ public class FriendsHub : Hub
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = _currentUserService.GetCurrentUserId();
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
var friendship = await _friendRequestService.SendAsync(userId, targetUserId);
|
||||
|
||||
await Clients.Group(targetUserId.ToString())
|
||||
@@ -111,7 +112,7 @@ public class FriendsHub : Hub
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = _currentUserService.GetCurrentUserId();
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
var friendship = await _friendRequestService.AcceptAsync(friendshipId, userId);
|
||||
await Clients.Group(userId.ToString())
|
||||
.SendAsync("FriendRequestAccepted", _mapper.Map<FriendshipDto>(friendship));
|
||||
@@ -140,7 +141,7 @@ public class FriendsHub : Hub
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = _currentUserService.GetCurrentUserId();
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
var friendship = await _friendRequestService.RejectAsync(friendshipId, userId);
|
||||
await Clients.Group(userId.ToString())
|
||||
.SendAsync("FriendRequestRejected", _mapper.Map<FriendshipDto>(friendship));
|
||||
@@ -164,21 +165,4 @@ public class FriendsHub : Hub
|
||||
return HubResult<object>.Error("Unexpected error! Please try later!");
|
||||
}
|
||||
}
|
||||
|
||||
private Guid GetUserId(bool suppressException = false)
|
||||
{
|
||||
var userIdClaim = Context.User?.FindFirst("userId")?.Value;
|
||||
if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
|
||||
{
|
||||
if (!suppressException)
|
||||
{
|
||||
_logger.LogError("Could not retrieve sender userId. Claim was: {UserIDClaim}", userIdClaim);
|
||||
throw new UnauthorizedAccessException("userID claim is missing or invalid.");
|
||||
}
|
||||
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.API.Common.SignalR.Helpers;
|
||||
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@@ -14,17 +13,23 @@ public class PresenceHub : Hub
|
||||
private readonly ILogger<PresenceHub> _logger;
|
||||
private readonly IUserNotificationScopeService _notificationScopeService;
|
||||
private readonly IOnlineUserStore _onlineUserStore;
|
||||
|
||||
public PresenceHub(ILogger<PresenceHub> logger, IUserNotificationScopeService notificationScopeService, IOnlineUserStore onlineUserStore)
|
||||
private readonly IHubUserAccessor _userAccessor;
|
||||
|
||||
public PresenceHub(
|
||||
ILogger<PresenceHub> logger,
|
||||
IUserNotificationScopeService notificationScopeService,
|
||||
IOnlineUserStore onlineUserStore,
|
||||
IHubUserAccessor userAccessor)
|
||||
{
|
||||
_logger = logger;
|
||||
_notificationScopeService = notificationScopeService;
|
||||
_onlineUserStore = onlineUserStore;
|
||||
_userAccessor = userAccessor;
|
||||
}
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
var userId = GetUserId();
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
if (userId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("User connected with invalid UserID claim.");
|
||||
@@ -48,7 +53,7 @@ public class PresenceHub : Hub
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
var userId = GetUserId();
|
||||
var userId = _userAccessor.GetUserId(Context, true);
|
||||
if (userId == Guid.Empty) return;
|
||||
|
||||
_onlineUserStore.SetOfflineUser(userId);
|
||||
@@ -63,21 +68,4 @@ public class PresenceHub : Hub
|
||||
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
|
||||
private Guid GetUserId(bool suppressException = false)
|
||||
{
|
||||
var userIdClaim = Context.User?.FindFirst("userId")?.Value;
|
||||
if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
|
||||
{
|
||||
if (!suppressException)
|
||||
{
|
||||
_logger.LogError("Could not retrieve sender userId. Claim was: {UserIDClaim}", userIdClaim);
|
||||
throw new UnauthorizedAccessException("userId claim is missing or invalid.");
|
||||
}
|
||||
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Text;
|
||||
using Govor.API.Extensions;
|
||||
using Govor.API.Common.Extensions;
|
||||
using Govor.API.Hubs;
|
||||
using Govor.Application.Services.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
|
||||
Reference in New Issue
Block a user