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;
using ColaFlow.Modules.Identity.Infrastructure.Persistence; using ColaFlow.Modules.Identity.Infrastructure.Persistence;
using ColaFlow.Modules.Mcp.Infrastructure.Extensions; using ColaFlow.Modules.Mcp.Infrastructure.Extensions;
using ColaFlow.Modules.Mcp.Infrastructure.Hubs;
using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence; using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;

View File

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