Initial commit

This commit is contained in:
2026-04-23 21:38:24 -04:00
commit 63e12a9636
1762 changed files with 1672620 additions and 0 deletions
@@ -0,0 +1,64 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
namespace PowderCoating.Web.Hubs;
/// <summary>
/// SignalR hub that delivers real-time notifications to authenticated users.
/// Each user is placed into a company-scoped SignalR group ("company-{companyId}") on connect so
/// that server-side code can broadcast to an entire tenant without iterating individual connections.
/// SuperAdmin users are also placed in the "superadmin" group for platform-wide alerts.
/// </summary>
[Authorize]
public class NotificationHub : Hub
{
private readonly ILogger<NotificationHub> _logger;
/// <summary>
/// Initializes a new instance of <see cref="NotificationHub"/> with the required logger.
/// </summary>
public NotificationHub(ILogger<NotificationHub> logger)
{
_logger = logger;
}
/// <summary>
/// Called by the SignalR framework when a client establishes a connection.
/// Reads the CompanyId claim from the authenticated user and adds the connection to the
/// corresponding company group. SuperAdmins are additionally added to the "superadmin" group.
/// Errors are caught and logged rather than re-thrown so that a group-join failure does not
/// abort the connection handshake for the client.
/// </summary>
public override async Task OnConnectedAsync()
{
try
{
var companyId = Context.User?.FindFirst("CompanyId")?.Value;
if (!string.IsNullOrEmpty(companyId))
await Groups.AddToGroupAsync(Context.ConnectionId, $"company-{companyId}");
if (Context.User?.IsInRole("SuperAdmin") == true)
await Groups.AddToGroupAsync(Context.ConnectionId, "superadmin");
await base.OnConnectedAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in NotificationHub.OnConnectedAsync for connection {ConnectionId}", Context.ConnectionId);
}
}
/// <summary>
/// Called by the SignalR framework when a client disconnects, either cleanly or due to an error.
/// Unexpected disconnects (non-null exception) are logged at Warning level so they appear in
/// monitoring without spamming the error log during normal browser refreshes.
/// </summary>
public override async Task OnDisconnectedAsync(Exception? exception)
{
if (exception != null)
_logger.LogWarning(exception, "NotificationHub client disconnected with error: {ConnectionId}", Context.ConnectionId);
await base.OnDisconnectedAsync(exception);
}
}
+60
View File
@@ -0,0 +1,60 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
namespace PowderCoating.Web.Hubs;
/// <summary>
/// SignalR hub that delivers real-time shop floor updates to TV displays and worker devices.
/// Each connection is placed in a company-scoped group ("shop-{companyId}") so that job status
/// changes, batch assignments, and oven updates can be broadcast to all shop floor screens for
/// a tenant without leaking data to other companies.
/// </summary>
[Authorize]
public class ShopHub : Hub
{
private readonly ILogger<ShopHub> _logger;
/// <summary>
/// Initializes a new instance of <see cref="ShopHub"/> with the required logger.
/// </summary>
public ShopHub(ILogger<ShopHub> logger)
{
_logger = logger;
}
/// <summary>
/// Called by the SignalR framework when a shop floor client connects.
/// Reads the CompanyId claim and adds the connection to the company-scoped shop group so that
/// server-side broadcasts reach all screens for that company only.
/// Errors are swallowed after logging to prevent a failed group join from dropping the connection.
/// </summary>
public override async Task OnConnectedAsync()
{
try
{
var companyId = Context.User?.FindFirst("CompanyId")?.Value;
if (!string.IsNullOrEmpty(companyId))
await Groups.AddToGroupAsync(Context.ConnectionId, $"shop-{companyId}");
await base.OnConnectedAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in ShopHub.OnConnectedAsync for connection {ConnectionId}", Context.ConnectionId);
}
}
/// <summary>
/// Called by the SignalR framework when a shop floor client disconnects.
/// Unexpected disconnects (e.g., TV display losing network) are logged at Warning level so
/// they can be correlated with shop floor connectivity issues without flooding the error log.
/// </summary>
public override async Task OnDisconnectedAsync(Exception? exception)
{
if (exception != null)
_logger.LogWarning(exception, "ShopHub client disconnected with error: {ConnectionId}", Context.ConnectionId);
await base.OnDisconnectedAsync(exception);
}
}