fix(backend): Move McpNotificationHub to Infrastructure layer to fix dependency inversion violation
Some checks failed
Code Coverage / Generate Coverage Report (push) Has been cancelled
Tests / Run Tests (9.0.x) (push) Has been cancelled
Tests / Docker Build Test (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled

Fixed compilation error where Infrastructure layer was referencing API layer (ColaFlow.API.Hubs).
This violated the dependency inversion principle and Clean Architecture layering rules.

Changes:
- Moved McpNotificationHub from ColaFlow.API/Hubs to ColaFlow.Modules.Mcp.Infrastructure/Hubs
- Updated McpNotificationHub to inherit directly from Hub instead of BaseHub
- Copied necessary helper methods (GetCurrentUserId, GetCurrentTenantId, GetTenantGroupName) to avoid cross-layer dependency
- Updated McpNotificationService to use new namespace (ColaFlow.Modules.Mcp.Infrastructure.Hubs)
- Updated Program.cs to import new Hub namespace
- Updated McpNotificationServiceTests to use new namespace
- Kept BaseHub in API layer for ProjectHub and NotificationHub

Architecture Impact:
- Infrastructure layer no longer depends on API layer
- Proper dependency flow: API -> Infrastructure -> Application -> Domain
- McpNotificationHub is now properly encapsulated within the MCP module

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Yaojia Wang
2025-11-09 18:37:08 +01:00
parent 61e0f1249c
commit 1d6e732018
4 changed files with 55 additions and 16 deletions

View File

@@ -7,6 +7,7 @@ using ColaFlow.Modules.Identity.Application;
using ColaFlow.Modules.Identity.Infrastructure;
using ColaFlow.Modules.Identity.Infrastructure.Persistence;
using ColaFlow.Modules.Mcp.Infrastructure.Extensions;
using ColaFlow.Modules.Mcp.Infrastructure.Hubs;
using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;

View File

@@ -1,14 +1,15 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
namespace ColaFlow.API.Hubs;
namespace ColaFlow.Modules.Mcp.Infrastructure.Hubs;
/// <summary>
/// SignalR Hub for MCP real-time notifications
/// Supports notifying AI agents and users about PendingChange status updates
/// </summary>
[Authorize]
public class McpNotificationHub : BaseHub
public class McpNotificationHub : Hub
{
private readonly ILogger<McpNotificationHub> _logger;
@@ -19,15 +20,26 @@ public class McpNotificationHub : BaseHub
public override async Task OnConnectedAsync()
{
var connectionId = Context.ConnectionId;
var userId = GetCurrentUserId();
var tenantId = GetCurrentTenantId();
try
{
var connectionId = Context.ConnectionId;
var userId = GetCurrentUserId();
var tenantId = GetCurrentTenantId();
_logger.LogInformation(
"MCP client connected - ConnectionId={ConnectionId}, UserId={UserId}, TenantId={TenantId}",
connectionId, userId, tenantId);
// Automatically join tenant group (tenant isolation)
await Groups.AddToGroupAsync(Context.ConnectionId, GetTenantGroupName(tenantId));
await base.OnConnectedAsync();
_logger.LogInformation(
"MCP client connected - ConnectionId={ConnectionId}, UserId={UserId}, TenantId={TenantId}",
connectionId, userId, tenantId);
await base.OnConnectedAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "MCP client connection error");
Context.Abort();
}
}
public override async Task OnDisconnectedAsync(Exception? exception)
@@ -78,11 +90,37 @@ public class McpNotificationHub : BaseHub
Context.ConnectionId, groupName, pendingChangeId);
}
/// <summary>
/// Get the SignalR group name for a pending change
/// </summary>
/// <param name="pendingChangeId">The pending change ID</param>
/// <returns>The group name</returns>
// Helper methods (copied from BaseHub to avoid cross-layer dependency)
protected Guid GetCurrentUserId()
{
var userIdClaim = Context.User?.FindFirst("sub")
?? Context.User?.FindFirst("user_id");
if (userIdClaim == null || !Guid.TryParse(userIdClaim.Value, out var userId))
{
throw new UnauthorizedAccessException("User ID not found in token");
}
return userId;
}
protected Guid GetCurrentTenantId()
{
var tenantIdClaim = Context.User?.FindFirst("tenant_id");
if (tenantIdClaim == null || !Guid.TryParse(tenantIdClaim.Value, out var tenantId))
{
throw new UnauthorizedAccessException("Tenant ID not found in token");
}
return tenantId;
}
protected string GetTenantGroupName(Guid tenantId)
{
return $"tenant-{tenantId}";
}
private static string GetPendingChangeGroupName(Guid pendingChangeId)
{
return $"pending-change-{pendingChangeId}";

View File

@@ -1,6 +1,6 @@
using ColaFlow.API.Hubs;
using ColaFlow.Modules.Mcp.Application.DTOs.Notifications;
using ColaFlow.Modules.Mcp.Application.Services;
using ColaFlow.Modules.Mcp.Infrastructure.Hubs;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;

View File

@@ -1,5 +1,5 @@
using ColaFlow.API.Hubs;
using ColaFlow.Modules.Mcp.Application.DTOs.Notifications;
using ColaFlow.Modules.Mcp.Infrastructure.Hubs;
using ColaFlow.Modules.Mcp.Infrastructure.Services;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;