using GB5Shared.DTO.Framework.Login; using Microsoft.AspNetCore.SignalR; using MMDAL.DTO.MRPRun; namespace MMSL.Hubs { // Group convention: "mrprun:ou:{workOUId}" — OU-scoped broadcast public class MRPRunHub : Hub { private readonly ILogger _logger; public MRPRunHub(ILogger logger) { _logger = logger; } public override async Task OnConnectedAsync() { var login = GetLoginDTO(); await Groups.AddToGroupAsync(Context.ConnectionId, HubGroup(login)); _logger.LogInformation("MRPRunHub connected: {ConnectionId} OU {WorkOUId}", Context.ConnectionId, login.WorkOUId); await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception? exception) { var login = GetLoginDTO(); await Groups.RemoveFromGroupAsync(Context.ConnectionId, HubGroup(login)); if (exception is not null) _logger.LogWarning(exception, "MRPRunHub disconnected with error: {ConnectionId}", Context.ConnectionId); await base.OnDisconnectedAsync(exception); } private LoginDTO GetLoginDTO() { var httpContext = Context.GetHttpContext() ?? throw new InvalidOperationException("Hub invoked outside HTTP context."); return FromHttpContext(httpContext); } public static LoginDTO FromHttpContext(HttpContext httpContext) { return new LoginDTO { UserId = Convert.ToInt32(httpContext.User?.FindFirst("UserId")?.Value ?? "-1"), UserName = httpContext.User?.FindFirst("UserName")?.Value ?? "", RoleId = Convert.ToInt32(httpContext.User?.FindFirst("RoleId")?.Value ?? "-1"), WorkOUId = Convert.ToInt32(httpContext.User?.FindFirst("WorkOUId")?.Value ?? "-1"), ClientId = Convert.ToInt32(httpContext.User?.FindFirst("TenantId")?.Value ?? "-1"), MachineIP = httpContext.Connection.RemoteIpAddress?.ToString() ?? "" }; } public static string HubGroup(LoginDTO login) => $"mrprun:ou:{login.WorkOUId}"; public static string HubGroup(int workOUId) => $"mrprun:ou:{workOUId}"; } }