diff --git a/src/Api/Data/Interfaces/IIssueRepository.cs b/src/Api/Data/Interfaces/IIssueRepository.cs
index 323a10a..dc5650d 100644
--- a/src/Api/Data/Interfaces/IIssueRepository.cs
+++ b/src/Api/Data/Interfaces/IIssueRepository.cs
@@ -42,8 +42,10 @@ public interface IIssueRepository
/// The number of items per page.
/// Optional search term to filter by title or description.
/// Optional author name to filter by.
+ /// Optional status name to filter by.
+ /// Optional category name to filter by.
/// Cancellation token.
- Task Items, long Total)>> GetAllAsync(int page, int pageSize, string? searchTerm = null, string? authorName = null, CancellationToken cancellationToken = default);
+ Task Items, long Total)>> GetAllAsync(int page, int pageSize, string? searchTerm = null, string? authorName = null, string? statusName = null, string? categoryName = null, CancellationToken cancellationToken = default);
///
/// Updates an existing issue in the database.
diff --git a/src/Api/Data/IssueRepository.cs b/src/Api/Data/IssueRepository.cs
index 916ed94..da2a5cf 100644
--- a/src/Api/Data/IssueRepository.cs
+++ b/src/Api/Data/IssueRepository.cs
@@ -72,6 +72,8 @@ public async Task>> GetAllAsync(CancellationToken
int pageSize,
string? searchTerm = null,
string? authorName = null,
+ string? statusName = null,
+ string? categoryName = null,
CancellationToken cancellationToken = default)
{
var filterBuilder = Builders.Filter;
@@ -94,6 +96,16 @@ public async Task>> GetAllAsync(CancellationToken
filters.Add(filterBuilder.Regex(x => x.Author.Name, new BsonRegularExpression(authorName, "i")));
}
+ if (!string.IsNullOrWhiteSpace(statusName))
+ {
+ filters.Add(filterBuilder.Regex(x => x.Status.StatusName, new BsonRegularExpression(statusName, "i")));
+ }
+
+ if (!string.IsNullOrWhiteSpace(categoryName))
+ {
+ filters.Add(filterBuilder.Regex(x => x.Category.CategoryName, new BsonRegularExpression(categoryName, "i")));
+ }
+
var filter = filterBuilder.And(filters);
var total = await _collection.CountDocumentsAsync(filter, cancellationToken: cancellationToken);
var entities = await _collection
diff --git a/src/Api/Handlers/Issues/IssueEndpoints.cs b/src/Api/Handlers/Issues/IssueEndpoints.cs
index 239b0f0..b41ee0e 100644
--- a/src/Api/Handlers/Issues/IssueEndpoints.cs
+++ b/src/Api/Handlers/Issues/IssueEndpoints.cs
@@ -21,14 +21,16 @@ public static IEndpointRouteBuilder MapIssueEndpoints(this IEndpointRouteBuilder
var group = app.MapGroup("/api/v1/issues").WithTags("Issues");
// List Issues (paginated)
- group.MapGet("", async (int? page, int? pageSize, string? searchTerm, string? authorName, ListIssuesHandler handler) =>
+ group.MapGet("", async (int? page, int? pageSize, string? searchTerm, string? authorName, string? statusName, string? categoryName, ListIssuesHandler handler) =>
{
var query = new ListIssuesQuery
{
Page = page ?? 1,
PageSize = pageSize ?? 20,
SearchTerm = searchTerm,
- AuthorName = authorName
+ AuthorName = authorName,
+ StatusName = statusName,
+ CategoryName = categoryName
};
var result = await handler.Handle(query);
return Results.Ok(result);
diff --git a/src/Api/Handlers/Issues/ListIssuesHandler.cs b/src/Api/Handlers/Issues/ListIssuesHandler.cs
index 4c40615..92e1566 100644
--- a/src/Api/Handlers/Issues/ListIssuesHandler.cs
+++ b/src/Api/Handlers/Issues/ListIssuesHandler.cs
@@ -35,7 +35,7 @@ public async Task> Handle(ListIssuesQuery query, Can
if (!validationResult.IsValid)
throw new ValidationException(validationResult.Errors);
- var result = await _repository.GetAllAsync(query.Page, query.PageSize, query.SearchTerm, query.AuthorName, cancellationToken);
+ var result = await _repository.GetAllAsync(query.Page, query.PageSize, query.SearchTerm, query.AuthorName, query.StatusName, query.CategoryName, cancellationToken);
var (items, total) = result.Value;
return new PaginatedResponse(items, total, query.Page, query.PageSize);
diff --git a/src/Shared/Contracts/ListIssuesQuery.cs b/src/Shared/Contracts/ListIssuesQuery.cs
index 6b8d842..c0cb71f 100644
--- a/src/Shared/Contracts/ListIssuesQuery.cs
+++ b/src/Shared/Contracts/ListIssuesQuery.cs
@@ -32,4 +32,14 @@ public record ListIssuesQuery
/// Gets or sets the author name for filtering by author.
///
public string? AuthorName { get; init; }
+
+ ///
+ /// Gets or sets the status name for filtering by status.
+ ///
+ public string? StatusName { get; init; }
+
+ ///
+ /// Gets or sets the category name for filtering by category.
+ ///
+ public string? CategoryName { get; init; }
}
diff --git a/src/Web/Components/Features/Issues/IssueApiClient.cs b/src/Web/Components/Features/Issues/IssueApiClient.cs
index 0a9e512..5417574 100644
--- a/src/Web/Components/Features/Issues/IssueApiClient.cs
+++ b/src/Web/Components/Features/Issues/IssueApiClient.cs
@@ -17,8 +17,10 @@ public interface IIssueApiClient
/// The number of items per page.
/// Optional search term to filter by title or description.
/// Optional author name to filter by.
+ /// Optional status name to filter by.
+ /// Optional category name to filter by.
/// Cancellation token.
- Task> GetAllAsync(int page = 1, int pageSize = 20, string? searchTerm = null, string? authorName = null, CancellationToken cancellationToken = default);
+ Task> GetAllAsync(int page = 1, int pageSize = 20, string? searchTerm = null, string? authorName = null, string? statusName = null, string? categoryName = null, CancellationToken cancellationToken = default);
/// Gets an issue by its identifier.
Task GetByIdAsync(string id, CancellationToken cancellationToken = default);
@@ -45,7 +47,7 @@ public class IssueApiClient : IIssueApiClient
public IssueApiClient(HttpClient httpClient) => _httpClient = httpClient;
///
- public async Task> GetAllAsync(int page = 1, int pageSize = 20, string? searchTerm = null, string? authorName = null, CancellationToken cancellationToken = default)
+ public async Task> GetAllAsync(int page = 1, int pageSize = 20, string? searchTerm = null, string? authorName = null, string? statusName = null, string? categoryName = null, CancellationToken cancellationToken = default)
{
try
{
@@ -58,6 +60,14 @@ public async Task> GetAllAsync(int page = 1, int pag
{
url += $"&authorName={Uri.EscapeDataString(authorName)}";
}
+ if (!string.IsNullOrWhiteSpace(statusName))
+ {
+ url += $"&statusName={Uri.EscapeDataString(statusName)}";
+ }
+ if (!string.IsNullOrWhiteSpace(categoryName))
+ {
+ url += $"&categoryName={Uri.EscapeDataString(categoryName)}";
+ }
var result = await _httpClient.GetFromJsonAsync>(url, cancellationToken).ConfigureAwait(false);
return result ?? PaginatedResponse.Empty;
diff --git a/src/Web/Components/Features/Issues/IssuesPage.razor b/src/Web/Components/Features/Issues/IssuesPage.razor
index 068efbc..a7f470b 100644
--- a/src/Web/Components/Features/Issues/IssuesPage.razor
+++ b/src/Web/Components/Features/Issues/IssuesPage.razor
@@ -153,7 +153,7 @@
_currentPage = page;
try
{
- var response = await IssueClient.GetAllAsync(page, 20);
+ var response = await IssueClient.GetAllAsync(page, 20, _searchTerm, null, _statusFilter, _categoryFilter);
_issues = response.Items;
_totalPages = response.TotalPages;
}
diff --git a/tests/Api.Tests.Unit/Endpoints/IssueEndpointsTests.cs b/tests/Api.Tests.Unit/Endpoints/IssueEndpointsTests.cs
index 9579141..c0beedf 100644
--- a/tests/Api.Tests.Unit/Endpoints/IssueEndpointsTests.cs
+++ b/tests/Api.Tests.Unit/Endpoints/IssueEndpointsTests.cs
@@ -39,7 +39,7 @@ public async Task ListIssues_ReturnsOk()
// Arrange
IReadOnlyList items = [];
_factory.IssueRepository
- .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns(Result<(IReadOnlyList Items, long Total)>.Ok((items, 0L)));
// Act
diff --git a/tests/Api.Tests.Unit/Handlers/Issues/ListIssuesHandlerTests.cs b/tests/Api.Tests.Unit/Handlers/Issues/ListIssuesHandlerTests.cs
index bbc29d0..3bb55d3 100644
--- a/tests/Api.Tests.Unit/Handlers/Issues/ListIssuesHandlerTests.cs
+++ b/tests/Api.Tests.Unit/Handlers/Issues/ListIssuesHandlerTests.cs
@@ -33,7 +33,7 @@ public async Task Handle_DefaultPagination_ReturnsFirstPageWithCorrectMetadata()
var query = new ListIssuesQuery { Page = 1, PageSize = 20 };
var issues = GenerateIssueDtos(20);
- _repository.GetAllAsync(1, 20, null, null, Arg.Any())
+ _repository.GetAllAsync(1, 20, null, null, null, null, Arg.Any())
.Returns(((IReadOnlyList)issues, 42L));
// Act
@@ -54,7 +54,7 @@ public async Task Handle_SecondPage_ReturnsCorrectItems()
var query = new ListIssuesQuery { Page = 2, PageSize = 10 };
var issues = GenerateIssueDtos(10);
- _repository.GetAllAsync(2, 10, null, null, Arg.Any())
+ _repository.GetAllAsync(2, 10, null, null, null, null, Arg.Any())
.Returns(((IReadOnlyList)issues, 42L));
// Act
@@ -75,7 +75,7 @@ public async Task Handle_LastPagePartialItems_ReturnsCorrectCount()
var query = new ListIssuesQuery { Page = 3, PageSize = 20 };
var issues = GenerateIssueDtos(2); // Last page has only 2 items
- _repository.GetAllAsync(3, 20, null, null, Arg.Any())
+ _repository.GetAllAsync(3, 20, null, null, null, null, Arg.Any())
.Returns(((IReadOnlyList)issues, 42L));
// Act
@@ -95,7 +95,7 @@ public async Task Handle_EmptyResult_ReturnsEmptyList()
// Arrange
var query = new ListIssuesQuery { Page = 1, PageSize = 20 };
- _repository.GetAllAsync(1, 20, null, null, Arg.Any())
+ _repository.GetAllAsync(1, 20, null, null, null, null, Arg.Any())
.Returns(((IReadOnlyList)new List(), 0L));
// Act
@@ -115,7 +115,7 @@ public async Task Handle_PageExceedsTotalPages_ReturnsEmptyList()
// Arrange
var query = new ListIssuesQuery { Page = 10, PageSize = 20 };
- _repository.GetAllAsync(10, 20, null, null, Arg.Any())
+ _repository.GetAllAsync(10, 20, null, null, null, null, Arg.Any())
.Returns(((IReadOnlyList)new List(), 42L));
// Act
@@ -176,14 +176,14 @@ public async Task Handle_ExcludesArchivedIssues_ByDefault()
var query = new ListIssuesQuery { Page = 1, PageSize = 20 };
var issues = GenerateIssueDtos(10);
- _repository.GetAllAsync(1, 20, null, null, Arg.Any())
+ _repository.GetAllAsync(1, 20, null, null, null, null, Arg.Any())
.Returns(((IReadOnlyList)issues, 10L));
// Act
var result = await _handler.Handle(query, CancellationToken.None);
// Assert
- await _repository.Received(1).GetAllAsync(1, 20, null, null, Arg.Any());
+ await _repository.Received(1).GetAllAsync(1, 20, null, null, null, null, Arg.Any());
result.Items.Should().HaveCount(10);
}
@@ -201,7 +201,7 @@ public async Task Handle_OrdersByCreatedAtDescending_NewestFirst()
new(ObjectId.GenerateNewId(), "Issue 1", "Desc", DateTime.UtcNow.AddDays(-3), null, UserDto.Empty, CategoryDto.Empty, StatusDto.Empty, false, UserDto.Empty, false, false),
};
- _repository.GetAllAsync(1, 3, null, null, Arg.Any())
+ _repository.GetAllAsync(1, 3, null, null, null, null, Arg.Any())
.Returns(((IReadOnlyList)orderedIssues, 3L));
// Act