Project Init
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
using FluentValidation;
|
||||
using MediatR;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.Behaviors;
|
||||
|
||||
/// <summary>
|
||||
/// Pipeline behavior for request validation using FluentValidation
|
||||
/// </summary>
|
||||
public sealed class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : IRequest<TResponse>
|
||||
{
|
||||
private readonly IEnumerable<IValidator<TRequest>> _validators;
|
||||
|
||||
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
|
||||
{
|
||||
_validators = validators;
|
||||
}
|
||||
|
||||
public async Task<TResponse> Handle(
|
||||
TRequest request,
|
||||
RequestHandlerDelegate<TResponse> next,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_validators.Any())
|
||||
{
|
||||
return await next();
|
||||
}
|
||||
|
||||
var context = new ValidationContext<TRequest>(request);
|
||||
|
||||
var validationResults = await Task.WhenAll(
|
||||
_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
|
||||
|
||||
var failures = validationResults
|
||||
.SelectMany(r => r.Errors)
|
||||
.Where(f => f != null)
|
||||
.ToList();
|
||||
|
||||
if (failures.Any())
|
||||
{
|
||||
throw new ValidationException(failures);
|
||||
}
|
||||
|
||||
return await next();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ColaFlow.Modules.ProjectManagement.Domain\ColaFlow.Modules.ProjectManagement.Domain.csproj" />
|
||||
<ProjectReference Include="..\ColaFlow.Modules.ProjectManagement.Contracts\ColaFlow.Modules.ProjectManagement.Contracts.csproj" />
|
||||
<ProjectReference Include="..\..\..\Shared\ColaFlow.Shared.Kernel\ColaFlow.Shared.Kernel.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="11.1.0" />
|
||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0" />
|
||||
<PackageReference Include="FluentValidation" Version="11.10.0" />
|
||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>ColaFlow.Modules.ProjectManagement.Application</AssemblyName>
|
||||
<RootNamespace>ColaFlow.Modules.ProjectManagement.Application</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,15 @@
|
||||
using MediatR;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.Commands.CreateEpic;
|
||||
|
||||
/// <summary>
|
||||
/// Command to create a new Epic
|
||||
/// </summary>
|
||||
public sealed record CreateEpicCommand : IRequest<EpicDto>
|
||||
{
|
||||
public Guid ProjectId { get; init; }
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string Description { get; init; } = string.Empty;
|
||||
public Guid CreatedBy { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using MediatR;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
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 : IRequestHandler<CreateEpicCommand, EpicDto>
|
||||
{
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public CreateEpicCommandHandler(
|
||||
IProjectRepository projectRepository,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository));
|
||||
_unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
|
||||
}
|
||||
|
||||
public async Task<EpicDto> Handle(CreateEpicCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// Get the project
|
||||
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
|
||||
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.Value,
|
||||
Priority = epic.Priority.Value,
|
||||
CreatedBy = epic.CreatedBy.Value,
|
||||
CreatedAt = epic.CreatedAt,
|
||||
UpdatedAt = epic.UpdatedAt,
|
||||
Stories = new List<StoryDto>()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.Commands.CreateEpic;
|
||||
|
||||
/// <summary>
|
||||
/// Validator for CreateEpicCommand
|
||||
/// </summary>
|
||||
public sealed class CreateEpicCommandValidator : AbstractValidator<CreateEpicCommand>
|
||||
{
|
||||
public CreateEpicCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.ProjectId)
|
||||
.NotEmpty().WithMessage("Project ID is required");
|
||||
|
||||
RuleFor(x => x.Name)
|
||||
.NotEmpty().WithMessage("Epic name is required")
|
||||
.MaximumLength(200).WithMessage("Epic name cannot exceed 200 characters");
|
||||
|
||||
RuleFor(x => x.CreatedBy)
|
||||
.NotEmpty().WithMessage("Created by user ID is required");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MediatR;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.Commands.CreateProject;
|
||||
|
||||
/// <summary>
|
||||
/// Command to create a new project
|
||||
/// </summary>
|
||||
public sealed record CreateProjectCommand : IRequest<ProjectDto>
|
||||
{
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string Description { get; init; } = string.Empty;
|
||||
public string Key { get; init; } = string.Empty;
|
||||
public Guid OwnerId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using MediatR;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.Commands.CreateProject;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for CreateProjectCommand
|
||||
/// </summary>
|
||||
public sealed class CreateProjectCommandHandler : IRequestHandler<CreateProjectCommand, ProjectDto>
|
||||
{
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public CreateProjectCommandHandler(
|
||||
IProjectRepository projectRepository,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository));
|
||||
_unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
|
||||
}
|
||||
|
||||
public async Task<ProjectDto> Handle(CreateProjectCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// Check if project key already exists
|
||||
var existingProject = await _projectRepository.GetByKeyAsync(request.Key, cancellationToken);
|
||||
if (existingProject != null)
|
||||
{
|
||||
throw new DomainException($"Project with key '{request.Key}' already exists");
|
||||
}
|
||||
|
||||
// Create project aggregate
|
||||
var project = Project.Create(
|
||||
request.Name,
|
||||
request.Description,
|
||||
request.Key,
|
||||
UserId.From(request.OwnerId)
|
||||
);
|
||||
|
||||
// Save to repository
|
||||
await _projectRepository.AddAsync(project, cancellationToken);
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// Return DTO
|
||||
return MapToDto(project);
|
||||
}
|
||||
|
||||
private static ProjectDto MapToDto(Project project)
|
||||
{
|
||||
return new ProjectDto
|
||||
{
|
||||
Id = project.Id.Value,
|
||||
Name = project.Name,
|
||||
Description = project.Description,
|
||||
Key = project.Key.Value,
|
||||
Status = project.Status.Name,
|
||||
OwnerId = project.OwnerId.Value,
|
||||
CreatedAt = project.CreatedAt,
|
||||
UpdatedAt = project.UpdatedAt,
|
||||
Epics = new List<EpicDto>()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.Commands.CreateProject;
|
||||
|
||||
/// <summary>
|
||||
/// Validator for CreateProjectCommand
|
||||
/// </summary>
|
||||
public sealed class CreateProjectCommandValidator : AbstractValidator<CreateProjectCommand>
|
||||
{
|
||||
public CreateProjectCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Name)
|
||||
.NotEmpty().WithMessage("Project name is required")
|
||||
.MaximumLength(200).WithMessage("Project name cannot exceed 200 characters");
|
||||
|
||||
RuleFor(x => x.Key)
|
||||
.NotEmpty().WithMessage("Project key is required")
|
||||
.MaximumLength(20).WithMessage("Project key cannot exceed 20 characters")
|
||||
.Matches("^[A-Z0-9]+$").WithMessage("Project key must contain only uppercase letters and numbers");
|
||||
|
||||
RuleFor(x => x.OwnerId)
|
||||
.NotEmpty().WithMessage("Owner ID is required");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using MediatR;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.Commands.UpdateEpic;
|
||||
|
||||
/// <summary>
|
||||
/// Command to update an existing Epic
|
||||
/// </summary>
|
||||
public sealed record UpdateEpicCommand : IRequest<EpicDto>
|
||||
{
|
||||
public Guid EpicId { get; init; }
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string Description { get; init; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using MediatR;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.Commands.UpdateEpic;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for UpdateEpicCommand
|
||||
/// </summary>
|
||||
public sealed class UpdateEpicCommandHandler : IRequestHandler<UpdateEpicCommand, EpicDto>
|
||||
{
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public UpdateEpicCommandHandler(
|
||||
IProjectRepository projectRepository,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository));
|
||||
_unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
|
||||
}
|
||||
|
||||
public async Task<EpicDto> Handle(UpdateEpicCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// Get the project containing the epic
|
||||
var epicId = EpicId.From(request.EpicId);
|
||||
var project = await _projectRepository.GetProjectWithEpicAsync(epicId, cancellationToken);
|
||||
|
||||
if (project == null)
|
||||
throw new NotFoundException("Epic", request.EpicId);
|
||||
|
||||
// Find the epic
|
||||
var epic = project.Epics.FirstOrDefault(e => e.Id == epicId);
|
||||
if (epic == null)
|
||||
throw new NotFoundException("Epic", request.EpicId);
|
||||
|
||||
// Update epic through domain method
|
||||
epic.UpdateDetails(request.Name, request.Description);
|
||||
|
||||
// Save changes
|
||||
_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.Value,
|
||||
Priority = epic.Priority.Value,
|
||||
CreatedBy = epic.CreatedBy.Value,
|
||||
CreatedAt = epic.CreatedAt,
|
||||
UpdatedAt = epic.UpdatedAt,
|
||||
Stories = epic.Stories.Select(s => new StoryDto
|
||||
{
|
||||
Id = s.Id.Value,
|
||||
Title = s.Title,
|
||||
Description = s.Description,
|
||||
EpicId = s.EpicId.Value,
|
||||
Status = s.Status.Value,
|
||||
Priority = s.Priority.Value,
|
||||
EstimatedHours = s.EstimatedHours,
|
||||
ActualHours = s.ActualHours,
|
||||
AssigneeId = s.AssigneeId?.Value,
|
||||
CreatedBy = s.CreatedBy.Value,
|
||||
CreatedAt = s.CreatedAt,
|
||||
UpdatedAt = s.UpdatedAt,
|
||||
Tasks = new List<TaskDto>()
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.Commands.UpdateEpic;
|
||||
|
||||
/// <summary>
|
||||
/// Validator for UpdateEpicCommand
|
||||
/// </summary>
|
||||
public sealed class UpdateEpicCommandValidator : AbstractValidator<UpdateEpicCommand>
|
||||
{
|
||||
public UpdateEpicCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.EpicId)
|
||||
.NotEmpty().WithMessage("Epic ID is required");
|
||||
|
||||
RuleFor(x => x.Name)
|
||||
.NotEmpty().WithMessage("Epic name is required")
|
||||
.MaximumLength(200).WithMessage("Epic name cannot exceed 200 characters");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
|
||||
/// <summary>
|
||||
/// Data Transfer Object for Epic
|
||||
/// </summary>
|
||||
public record EpicDto
|
||||
{
|
||||
public Guid Id { get; init; }
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string Description { get; init; } = string.Empty;
|
||||
public Guid ProjectId { get; init; }
|
||||
public string Status { get; init; } = string.Empty;
|
||||
public string Priority { get; init; } = string.Empty;
|
||||
public Guid CreatedBy { get; init; }
|
||||
public DateTime CreatedAt { get; init; }
|
||||
public DateTime? UpdatedAt { get; init; }
|
||||
public List<StoryDto> Stories { get; init; } = new();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
|
||||
/// <summary>
|
||||
/// Data Transfer Object for Project
|
||||
/// </summary>
|
||||
public record ProjectDto
|
||||
{
|
||||
public Guid Id { get; init; }
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string Description { get; init; } = string.Empty;
|
||||
public string Key { get; init; } = string.Empty;
|
||||
public string Status { get; init; } = string.Empty;
|
||||
public Guid OwnerId { get; init; }
|
||||
public DateTime CreatedAt { get; init; }
|
||||
public DateTime? UpdatedAt { get; init; }
|
||||
public List<EpicDto> Epics { get; init; } = new();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
|
||||
/// <summary>
|
||||
/// Data Transfer Object for Story
|
||||
/// </summary>
|
||||
public record StoryDto
|
||||
{
|
||||
public Guid Id { get; init; }
|
||||
public string Title { get; init; } = string.Empty;
|
||||
public string Description { get; init; } = string.Empty;
|
||||
public Guid EpicId { get; init; }
|
||||
public string Status { get; init; } = string.Empty;
|
||||
public string Priority { get; init; } = string.Empty;
|
||||
public Guid? AssigneeId { get; init; }
|
||||
public decimal? EstimatedHours { get; init; }
|
||||
public decimal? ActualHours { get; init; }
|
||||
public Guid CreatedBy { get; init; }
|
||||
public DateTime CreatedAt { get; init; }
|
||||
public DateTime? UpdatedAt { get; init; }
|
||||
public List<TaskDto> Tasks { get; init; } = new();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
|
||||
/// <summary>
|
||||
/// Data Transfer Object for Task
|
||||
/// </summary>
|
||||
public record TaskDto
|
||||
{
|
||||
public Guid Id { get; init; }
|
||||
public string Title { get; init; } = string.Empty;
|
||||
public string Description { get; init; } = string.Empty;
|
||||
public Guid StoryId { get; init; }
|
||||
public string Status { get; init; } = string.Empty;
|
||||
public string Priority { get; init; } = string.Empty;
|
||||
public Guid? AssigneeId { get; init; }
|
||||
public decimal? EstimatedHours { get; init; }
|
||||
public decimal? ActualHours { get; init; }
|
||||
public Guid CreatedBy { get; init; }
|
||||
public DateTime CreatedAt { get; init; }
|
||||
public DateTime? UpdatedAt { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using MediatR;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.Queries.GetEpicById;
|
||||
|
||||
/// <summary>
|
||||
/// Query to get an Epic by its ID
|
||||
/// </summary>
|
||||
public sealed record GetEpicByIdQuery(Guid EpicId) : IRequest<EpicDto>;
|
||||
@@ -0,0 +1,9 @@
|
||||
using MediatR;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.Queries.GetProjectById;
|
||||
|
||||
/// <summary>
|
||||
/// Query to get a project by its ID
|
||||
/// </summary>
|
||||
public sealed record GetProjectByIdQuery(Guid ProjectId) : IRequest<ProjectDto>;
|
||||
@@ -0,0 +1,92 @@
|
||||
using MediatR;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
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.Queries.GetProjectById;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for GetProjectByIdQuery
|
||||
/// </summary>
|
||||
public sealed class GetProjectByIdQueryHandler : IRequestHandler<GetProjectByIdQuery, ProjectDto>
|
||||
{
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
|
||||
public GetProjectByIdQueryHandler(IProjectRepository projectRepository)
|
||||
{
|
||||
_projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository));
|
||||
}
|
||||
|
||||
public async Task<ProjectDto> Handle(GetProjectByIdQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var project = await _projectRepository.GetByIdAsync(
|
||||
ProjectId.From(request.ProjectId),
|
||||
cancellationToken);
|
||||
|
||||
if (project == null)
|
||||
{
|
||||
throw new DomainException($"Project with ID '{request.ProjectId}' not found");
|
||||
}
|
||||
|
||||
return MapToDto(project);
|
||||
}
|
||||
|
||||
private static ProjectDto MapToDto(Project project)
|
||||
{
|
||||
return new ProjectDto
|
||||
{
|
||||
Id = project.Id.Value,
|
||||
Name = project.Name,
|
||||
Description = project.Description,
|
||||
Key = project.Key.Value,
|
||||
Status = project.Status.Name,
|
||||
OwnerId = project.OwnerId.Value,
|
||||
CreatedAt = project.CreatedAt,
|
||||
UpdatedAt = project.UpdatedAt,
|
||||
Epics = project.Epics.Select(e => new EpicDto
|
||||
{
|
||||
Id = e.Id.Value,
|
||||
Name = e.Name,
|
||||
Description = e.Description,
|
||||
ProjectId = e.ProjectId.Value,
|
||||
Status = e.Status.Name,
|
||||
Priority = e.Priority.Name,
|
||||
CreatedBy = e.CreatedBy.Value,
|
||||
CreatedAt = e.CreatedAt,
|
||||
UpdatedAt = e.UpdatedAt,
|
||||
Stories = e.Stories.Select(s => new StoryDto
|
||||
{
|
||||
Id = s.Id.Value,
|
||||
Title = s.Title,
|
||||
Description = s.Description,
|
||||
EpicId = s.EpicId.Value,
|
||||
Status = s.Status.Name,
|
||||
Priority = s.Priority.Name,
|
||||
AssigneeId = s.AssigneeId?.Value,
|
||||
EstimatedHours = s.EstimatedHours,
|
||||
ActualHours = s.ActualHours,
|
||||
CreatedBy = s.CreatedBy.Value,
|
||||
CreatedAt = s.CreatedAt,
|
||||
UpdatedAt = s.UpdatedAt,
|
||||
Tasks = s.Tasks.Select(t => new TaskDto
|
||||
{
|
||||
Id = t.Id.Value,
|
||||
Title = t.Title,
|
||||
Description = t.Description,
|
||||
StoryId = t.StoryId.Value,
|
||||
Status = t.Status.Name,
|
||||
Priority = t.Priority.Name,
|
||||
AssigneeId = t.AssigneeId?.Value,
|
||||
EstimatedHours = t.EstimatedHours,
|
||||
ActualHours = t.ActualHours,
|
||||
CreatedBy = t.CreatedBy.Value,
|
||||
CreatedAt = t.CreatedAt,
|
||||
UpdatedAt = t.UpdatedAt
|
||||
}).ToList()
|
||||
}).ToList()
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using MediatR;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.Queries.GetProjects;
|
||||
|
||||
/// <summary>
|
||||
/// Query to get all projects
|
||||
/// </summary>
|
||||
public sealed record GetProjectsQuery : IRequest<List<ProjectDto>>;
|
||||
@@ -0,0 +1,43 @@
|
||||
using MediatR;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.Queries.GetProjects;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for GetProjectsQuery
|
||||
/// </summary>
|
||||
public sealed class GetProjectsQueryHandler : IRequestHandler<GetProjectsQuery, List<ProjectDto>>
|
||||
{
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
|
||||
public GetProjectsQueryHandler(IProjectRepository projectRepository)
|
||||
{
|
||||
_projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository));
|
||||
}
|
||||
|
||||
public async Task<List<ProjectDto>> Handle(GetProjectsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var projects = await _projectRepository.GetAllAsync(cancellationToken);
|
||||
|
||||
return projects.Select(MapToDto).ToList();
|
||||
}
|
||||
|
||||
private static ProjectDto MapToDto(Project project)
|
||||
{
|
||||
return new ProjectDto
|
||||
{
|
||||
Id = project.Id.Value,
|
||||
Name = project.Name,
|
||||
Description = project.Description,
|
||||
Key = project.Key.Value,
|
||||
Status = project.Status.Name,
|
||||
OwnerId = project.OwnerId.Value,
|
||||
CreatedAt = project.CreatedAt,
|
||||
UpdatedAt = project.UpdatedAt,
|
||||
// Don't load Epics for list view (performance)
|
||||
Epics = new List<EpicDto>()
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user