Files
ColaFlow/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Commands/CreateEpic/CreateEpicCommandHandler.cs
Yaojia Wang d2ed21873e refactor(backend): Remove ITenantContext from Command/Query Handlers
Fix architectural issue where tenant isolation logic was incorrectly placed
in the Application layer (Handlers) instead of the Infrastructure layer
(DbContext/Repository).

Changes:
- Removed ITenantContext injection from 12 Command/Query Handlers
- Removed manual tenant verification code from all handlers
- Tenant isolation now handled exclusively by Global Query Filters in PMDbContext
- Handlers now focus purely on business logic, not cross-cutting concerns

Architecture Benefits:
- Proper separation of concerns (Handler = business logic, DbContext = tenant filtering)
- Eliminates code duplication across handlers
- Follows Repository pattern correctly
- Single Responsibility Principle compliance
- Cleaner, more maintainable code

Affected Handlers:
- CreateEpicCommandHandler
- UpdateEpicCommandHandler
- CreateStoryCommandHandler
- UpdateStoryCommandHandler
- AssignStoryCommandHandler
- DeleteStoryCommandHandler
- CreateTaskCommandHandler
- UpdateTaskCommandHandler
- AssignTaskCommandHandler
- DeleteTaskCommandHandler
- UpdateTaskStatusCommandHandler
- GetEpicByIdQueryHandler

Technical Notes:
- PMDbContext already has Global Query Filters configured correctly
- Project aggregate passes TenantId when creating child entities
- Repository queries automatically filtered by tenant via EF Core filters

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 17:15:43 +01:00

54 lines
2.1 KiB
C#

using MediatR;
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
using ColaFlow.Modules.ProjectManagement.Application.Common.Interfaces;
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
using ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
namespace ColaFlow.Modules.ProjectManagement.Application.Commands.CreateEpic;
/// <summary>
/// Handler for CreateEpicCommand
/// </summary>
public sealed class CreateEpicCommandHandler(
IProjectRepository projectRepository,
IUnitOfWork unitOfWork)
: IRequestHandler<CreateEpicCommand, EpicDto>
{
private readonly IProjectRepository _projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository));
private readonly IUnitOfWork _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
public async Task<EpicDto> Handle(CreateEpicCommand request, CancellationToken cancellationToken)
{
// Get the project (Global Query Filter ensures tenant isolation)
var projectId = ProjectId.From(request.ProjectId);
var project = await _projectRepository.GetByIdAsync(projectId, cancellationToken);
if (project == null)
throw new NotFoundException("Project", request.ProjectId);
// Create epic through aggregate root (Project passes its TenantId)
var createdById = UserId.From(request.CreatedBy);
var epic = project.CreateEpic(request.Name, request.Description, createdById);
// Update project (epic is part of aggregate)
_projectRepository.Update(project);
await _unitOfWork.SaveChangesAsync(cancellationToken);
// Map to DTO
return new EpicDto
{
Id = epic.Id.Value,
Name = epic.Name,
Description = epic.Description,
ProjectId = epic.ProjectId.Value,
Status = epic.Status.Name,
Priority = epic.Priority.Name,
CreatedBy = epic.CreatedBy.Value,
CreatedAt = epic.CreatedAt,
UpdatedAt = epic.UpdatedAt,
Stories = new List<StoryDto>()
};
}
}