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>
60 lines
2.2 KiB
C#
60 lines
2.2 KiB
C#
using MediatR;
|
|
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;
|
|
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
|
|
|
|
namespace ColaFlow.Modules.ProjectManagement.Application.Commands.DeleteTask;
|
|
|
|
/// <summary>
|
|
/// Handler for DeleteTaskCommand
|
|
/// </summary>
|
|
public sealed class DeleteTaskCommandHandler(
|
|
IProjectRepository projectRepository,
|
|
IUnitOfWork unitOfWork)
|
|
: IRequestHandler<DeleteTaskCommand, Unit>
|
|
{
|
|
private readonly IProjectRepository _projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository));
|
|
private readonly IUnitOfWork _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
|
|
|
|
public async Task<Unit> Handle(DeleteTaskCommand request, CancellationToken cancellationToken)
|
|
{
|
|
// Get the project containing the task (Global Query Filter ensures tenant isolation)
|
|
var taskId = TaskId.From(request.TaskId);
|
|
var project = await _projectRepository.GetProjectWithTaskAsync(taskId, cancellationToken);
|
|
|
|
if (project == null)
|
|
throw new NotFoundException("Task", request.TaskId);
|
|
|
|
// Find the story containing the task
|
|
Story? parentStory = null;
|
|
foreach (var epic in project.Epics)
|
|
{
|
|
foreach (var story in epic.Stories)
|
|
{
|
|
var task = story.Tasks.FirstOrDefault(t => t.Id.Value == request.TaskId);
|
|
if (task != null)
|
|
{
|
|
parentStory = story;
|
|
break;
|
|
}
|
|
}
|
|
if (parentStory != null)
|
|
break;
|
|
}
|
|
|
|
if (parentStory == null)
|
|
throw new NotFoundException("Task", request.TaskId);
|
|
|
|
// Remove task from story
|
|
parentStory.RemoveTask(taskId);
|
|
|
|
// Update project
|
|
_projectRepository.Update(project);
|
|
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
|
|
|
return Unit.Value;
|
|
}
|
|
}
|