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>
54 lines
2.1 KiB
C#
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>()
|
|
};
|
|
}
|
|
}
|