Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions src/Api/Handlers/Issues/CreateIssueHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// Project Name : Api
// =======================================================

using Api.Data.Interfaces;
using Api.Services;

namespace Api.Handlers.Issues;
Expand All @@ -17,15 +18,24 @@ namespace Api.Handlers.Issues;
public class CreateIssueHandler
{
private readonly IIssueRepository _repository;
private readonly ICategoryRepository _categoryRepository;
private readonly IStatusRepository _statusRepository;
private readonly CreateIssueValidator _validator;
private readonly ICurrentUserService _currentUserService;

/// <summary>
/// Initializes a new instance of the <see cref="CreateIssueHandler"/> class.
/// </summary>
public CreateIssueHandler(IIssueRepository repository, CreateIssueValidator validator, ICurrentUserService currentUserService)
public CreateIssueHandler(
IIssueRepository repository,
ICategoryRepository categoryRepository,
IStatusRepository statusRepository,
CreateIssueValidator validator,
ICurrentUserService currentUserService)
{
_repository = repository;
_categoryRepository = categoryRepository;
_statusRepository = statusRepository;
_validator = validator;
_currentUserService = currentUserService;
}
Expand All @@ -43,15 +53,18 @@ public async Task<Result<IssueDto>> Handle(CreateIssueCommand command, Cancellat
? new UserDto(_currentUserService.UserId ?? string.Empty, _currentUserService.Name ?? string.Empty, _currentUserService.Email ?? string.Empty)
: UserDto.Empty;

var category = await LookupByIdAsync(command.CategoryId, CategoryDto.Empty, _categoryRepository.GetByIdAsync, cancellationToken);
var status = await LookupByIdAsync(command.StatusId, StatusDto.Empty, _statusRepository.GetByIdAsync, cancellationToken);

var model = new Issue
{
Id = ObjectId.GenerateNewId(),
Title = command.Title,
Description = command.Description ?? string.Empty,
DateCreated = DateTime.UtcNow,
Status = StatusDto.Empty,
Status = status,
Author = author,
Category = CategoryDto.Empty
Category = category
};

var result = await _repository.CreateAsync(model.ToDto(), cancellationToken);
Expand All @@ -60,4 +73,19 @@ public async Task<Result<IssueDto>> Handle(CreateIssueCommand command, Cancellat

return Result.Ok(result.Value!);
}

private static async Task<T> LookupByIdAsync<T>(
string? id,
T defaultValue,
Func<ObjectId, CancellationToken, Task<Result<T>>> getById,
CancellationToken cancellationToken)
{
if (!string.IsNullOrEmpty(id) && ObjectId.TryParse(id, out var objectId))
{
var result = await getById(objectId, cancellationToken);
if (result.Success && result.Value is not null)
return result.Value;
}
return defaultValue;
Comment on lines +83 to +89
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LookupByIdAsync has important branches for (a) non-empty but unparseable IDs and (b) parseable IDs where the repository returns NotFound/Fail. There’s currently coverage for the “present and found” and “null IDs” paths, but not for these new fallback branches. Consider adding unit tests to ensure invalid/not-found IDs don’t call CreateAsync with unintended values and correctly fall back to CategoryDto.Empty/StatusDto.Empty.

Copilot uses AI. Check for mistakes.
}
}
10 changes: 10 additions & 0 deletions src/Shared/Contracts/CreateIssueValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ public record CreateIssueCommand
/// Gets or sets the labels to attach to the issue.
/// </summary>
public List<string>? Labels { get; init; }

/// <summary>
/// Gets or sets the category ID for the issue.
/// </summary>
public string? CategoryId { get; init; }

/// <summary>
/// Gets or sets the status ID for the issue.
/// </summary>
public string? StatusId { get; init; }
}

/// <summary>
Expand Down
4 changes: 3 additions & 1 deletion src/Web/Components/Features/Issues/CreateIssuePage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
var command = new CreateIssueCommand
{
Title = request.Title,
Description = request.Description
Description = request.Description,
CategoryId = request.CategoryId,
StatusId = request.StatusId
};
await IssueClient.CreateAsync(command);
Nav.NavigateTo("/issues");
Expand Down
Loading
Loading