Skip to content

Added classes for CRD and chart - #159

Merged
maximgorbatyuk merged 2 commits into
mainfrom
currencies-crud
Jan 4, 2026
Merged

maximgorbatyuk merged 2 commits into
mainfrom
currencies-crud

Conversation

@maximgorbatyuk

@maximgorbatyuk maximgorbatyuk commented Jan 4, 2026 •

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added currency collection management endpoints: create, retrieve, and delete currency collections with admin-only access control.
    • Added weekly currency chart data endpoint to visualize currency trends and average values over time.
  • Tests

    • Added comprehensive test suite for currency collection creation handler with validation scenarios.
  • Refactor

    • Updated mediator service configuration for improved generic constraint handling.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 4, 2026 •

Copy link
Copy Markdown
Contributor

Walkthrough

Introduces a new currency collection management feature with four API endpoints for creating, retrieving, and deleting currency collections, plus a chart data aggregation endpoint. Includes handlers implementing mediator pattern, request/response DTOs, and comprehensive tests. Also simplifies a mediator service provider method's generic constraint from a fully qualified interface name to a simple type name.

Changes

Cohort / File(s) Summary
Mediator Infrastructure
src/Infrastructure/Services/Mediator/ServiceProviderExtensions.cs
Generic constraint simplified from fully qualified Infrastructure.Services.Mediator.IRequestHandler<TRequest, TResult> to IRequestHandler<TRequest, TResult> in HandleBy<THandler, TRequest, TResult> method.
Currency Collection Handlers
src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandler.cs, src/Web.Api/Features/Currencies/DeleteCurrenciesCollection/DeleteCurrenciesCollectionHandler.cs, src/Web.Api/Features/Currencies/GetCurrenciesCollection/GetCurrenciesCollectionHandler.cs, src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataHandler.cs
Four new mediator handlers: Create validates currencies and date, prevents duplicates, persists to database; Delete removes by ID; Get retrieves paginated collections sorted descending; Chart aggregates weekly averages over a one-year window with helper for week-start calculation.
Currency Collection Requests & Parameters
src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionRequest.cs, src/Web.Api/Features/Currencies/DeleteCurrenciesCollection/DeleteCurrenciesCollectionRequest.cs, src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataQueryParams.cs, src/Web.Api/Features/Currencies/GetCurrenciesCollection/GetCurrenciesCollectionQueryParams.cs
Request records for handler operations: Create contains currency dictionary and date; Delete holds ID; Chart query params is empty placeholder; Get query params extends PageModel for pagination.
Currency Collection Response & DTOs
src/Web.Api/Features/Currencies/Dtos/CurrenciesCollectionDto.cs, src/Web.Api/Features/Currencies/Dtos/CurrencyItemDto.cs, src/Web.Api/Features/Currencies/Dtos/WeeklyCurrencyChartDataDto.cs, src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataResponse.cs
Response objects: CurrenciesCollectionDto maps domain entity to API (includes constructor for entity conversion), CurrencyItemDto holds currency-value pair, WeeklyCurrencyChartDataDto wraps weekly averages with date range, GetCurrenciesChartDataResponse contains weekly data and overall date bounds.
Currency Collection Controller
src/Web.Api/Features/Currencies/CurrenciesCollectionController.cs
New controller exposing four endpoints: GET /api/currencies/chart (public, chart data), GET /api/currencies (admin, paginated list), POST /api/currencies (admin, create), DELETE /api/currencies/{id} (admin, delete); all delegate to handlers via mediator.
Currency Collection Tests
src/Web.Api.Tests/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandlerTests.cs
Test suite for CreateCurrenciesCollectionHandler with four scenarios: successful creation, duplicate date rejection, empty currency set rejection, future date rejection; validates message content and data integrity.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as CurrenciesCollectionController
    participant Handler as Handler<br/>(Create/Get/Delete)
    participant DB as DatabaseContext
    participant Mediator as ServiceProvider<br/>(HandleBy)

    rect rgba(100, 150, 200, 0.2)
    note over Client,Mediator: Standard CRUD Flow (Create/Get/Delete)
    Client->>Controller: HTTP Request<br/>(Create/Get/Delete)
    Controller->>Mediator: HandleBy<THandler><br/>(Request, CancellationToken)
    Mediator->>Handler: Handle(Request)
    Handler->>DB: Query/Command<br/>(Add/Remove/AsNoTracking)
    DB-->>Handler: Result
    Handler->>DB: SaveChangesAsync()
    DB-->>Handler: Confirmation
    Handler-->>Mediator: Response DTO
    Mediator-->>Controller: Response DTO
    Controller-->>Client: HTTP 200/201/204
    end

    rect rgba(150, 200, 100, 0.2)
    note over Handler,DB: GetCurrenciesChartData (Aggregation)
    Handler->>DB: Query CurrencyCollections<br/>(1-year window)
    DB-->>Handler: Records Enumerable
    Handler->>Handler: Group by Week
    loop For each week
        Handler->>Handler: Average currencies<br/>per group
        Handler->>Handler: Build WeeklyCurrencyChartDataDto
    end
    Handler->>Handler: Sort by WeekStartDate
    Handler-->>Controller: GetCurrenciesChartDataResponse<br/>(WeeklyData + DateRange)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Added classes for CRD and chart' is vague and uses abbreviated jargon (CRD, chart) without context, making it unclear what the changeset actually implements. Revise the title to be more descriptive, e.g., 'Add currencies collection CRUD operations and chart data handler' to clarify the feature being added.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@maximgorbatyuk
maximgorbatyuk merged commit 1927ad2 into main Jan 4, 2026
3 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (13)
src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataQueryParams.cs (1)

3-5: Consider deferring this type until query parameters are actually needed.

The empty record serves as a placeholder but adds no current value. Consider creating this type when actual query parameters (e.g., date ranges, filters) are introduced.

src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataHandler.cs (2)

24-67: Consider database-level aggregation for better performance.

The current implementation performs a two-phase query: first loading all records into memory, then grouping and averaging in C#. For better performance and scalability, consider pushing the aggregation logic to the database level using GroupBy directly on the IQueryable before materialization.

This would reduce memory footprint and leverage database query optimization.


31-35: Monitor query performance as data accumulates.

While the current data volume of approximately one year of daily currency records is manageable, consider monitoring this query if the data collection period extends or record volume increases. Pagination is not appropriate for chart endpoints that aggregate historical data, but server-side aggregation could be explored as an optional optimization if performance concerns arise.

src/Web.Api/Features/Currencies/Dtos/WeeklyCurrencyChartDataDto.cs (1)

12-12: Consider using IReadOnlyList for immutability.

The List<CurrencyItemDto> type is mutable, which conflicts with the immutable semantics of a record. Consider using IReadOnlyList<CurrencyItemDto> to prevent external modification of the collection after construction.

🔎 Proposed refactor for immutability
-public List<CurrencyItemDto> AverageCurrencies { get; }
+public IReadOnlyList<CurrencyItemDto> AverageCurrencies { get; }

If making this change, also update the constructor parameter type and any calling code in GetCurrenciesChartDataHandler.cs.

src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataResponse.cs (1)

9-9: Consider using IReadOnlyList for immutability.

Similar to WeeklyCurrencyChartDataDto, using List<T> allows external mutation of the collection. Consider IReadOnlyList<WeeklyCurrencyChartDataDto> to align with record immutability semantics.

src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionRequest.cs (3)

10-11: [Required] attribute doesn't validate empty dictionaries.

The [Required] attribute on a Dictionary only checks for null, not for empty collections. An empty currency dictionary would pass validation but might not represent valid business logic.

Consider adding a custom validation attribute or validation logic to ensure the dictionary contains at least one entry.

🔎 Proposed validation enhancement

Add a custom validation attribute:

using System.ComponentModel.DataAnnotations;

public class MinimumCountAttribute : ValidationAttribute
{
    private readonly int _minCount;

    public MinimumCountAttribute(int minCount)
    {
        _minCount = minCount;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value is IDictionary dictionary && dictionary.Count < _minCount)
        {
            return new ValidationResult($"The collection must contain at least {_minCount} item(s).");
        }
        return ValidationResult.Success;
    }
}

Then apply it:

 [Required]
+[MinimumCount(1, ErrorMessage = "At least one currency is required.")]
 public Dictionary<Currency, double> Currencies { get; init; }

13-14: Clarify timezone handling for CurrencyDate.

The DateTime type doesn't enforce UTC, which could lead to timezone-related bugs if the system assumes UTC but receives local time (or vice versa). Consider:

  • Using DateTimeOffset for timezone-aware dates
  • Documenting that the value must be UTC
  • Adding validation to ensure only UTC dates are accepted

11-11: Consider validating currency values.

The double values in the dictionary have no constraints. Consider whether negative values, NaN, or Infinity should be rejected with validation attributes or custom validation logic.

src/Web.Api.Tests/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandlerTests.cs (1)

93-114: Consider adding a test case for null Currencies property.

The handler validates both null and empty collections (request.Currencies == null || request.Currencies.Count == 0), but this test only covers the empty dictionary case. Adding a test with Currencies = null would ensure both branches are exercised.

🔎 Proposed additional test
[Fact]
public async Task Handle_NullCurrencies_ThrowsBadRequestException()
{
    // Arrange
    await using var context = new InMemoryDatabaseContext();
    var handler = new CreateCurrenciesCollectionHandler(context);

    var request = new CreateCurrenciesCollectionRequest
    {
        CurrencyDate = new DateTime(2024, 1, 15),
        Currencies = null
    };

    // Act & Assert
    var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
        handler.Handle(request, CancellationToken.None));

    Assert.Contains("at least one currency", exception.Message);
}
src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandler.cs (1)

27-30: Minor: Consider using DateTime directly instead of fully qualified System.DateTime.

Since there's no ambiguity in this file, you could add using System; at the top and use DateTime.UtcNow.Date for consistency.

src/Web.Api/Features/Currencies/GetCurrenciesCollection/GetCurrenciesCollectionHandler.cs (1)

12-20: Consider using primary constructor for consistency.

This handler uses a traditional constructor with explicit field assignment, while CreateCurrenciesCollectionHandler uses the C# 12 primary constructor syntax. For consistency across handlers in this feature, consider aligning the style.

🔎 Proposed refactor using primary constructor
-public class GetCurrenciesCollectionHandler
+public class GetCurrenciesCollectionHandler(DatabaseContext context)
     : IRequestHandler<GetCurrenciesCollectionQueryParams, Pageable<CurrenciesCollectionDto>>
 {
-    private readonly DatabaseContext _context;
-
-    public GetCurrenciesCollectionHandler(DatabaseContext context)
-    {
-        _context = context;
-    }
-
     public async Task<Pageable<CurrenciesCollectionDto>> Handle(
         GetCurrenciesCollectionQueryParams request,
         CancellationToken cancellationToken)
     {
-        return await _context.CurrencyCollections
+        return await context.CurrencyCollections
             .AsNoTracking()
src/Web.Api/Features/Currencies/Dtos/CurrenciesCollectionDto.cs (1)

8-22: Consider initializing Currencies to prevent potential null references.

The parameterless constructor doesn't initialize Currencies, leaving it null. Consumers of this DTO might assume Currencies is always a valid collection. Consider initializing it to an empty list or using required modifier.

🔎 Proposed fix
 public record CurrenciesCollectionDto
 {
     public Guid Id { get; init; }
 
-    public List<CurrencyItemDto> Currencies { get; init; }
+    public List<CurrencyItemDto> Currencies { get; init; } = new();
 
     public DateTime CurrencyDate { get; init; }
 
     public DateTimeOffset CreatedAt { get; init; }
 
     public DateTimeOffset UpdatedAt { get; init; }
-
-    public CurrenciesCollectionDto()
-    {
-    }
src/Web.Api/Features/Currencies/CurrenciesCollectionController.cs (1)

61-70: Consider returning IActionResult with NoContent() for delete operations.

Returning bool from a delete endpoint is unconventional. RESTful APIs typically return 204 No Content on successful deletion. This also provides better semantics for the HTTP response.

🔎 Proposed refactor
     [HttpDelete("{id:guid}")]
     [HasAnyRole(Role.Admin)]
-    public Task<bool> Delete(
+    public async Task<IActionResult> Delete(
         [FromRoute] Guid id,
         CancellationToken cancellationToken)
     {
-        return _serviceProvider.HandleBy<DeleteCurrenciesCollectionHandler, DeleteCurrenciesCollectionRequest, bool>(
+        await _serviceProvider.HandleBy<DeleteCurrenciesCollectionHandler, DeleteCurrenciesCollectionRequest, bool>(
             new DeleteCurrenciesCollectionRequest { Id = id },
             cancellationToken);
+        return NoContent();
     }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 041c36c and 0fc24e3.

📒 Files selected for processing (15)
  • src/Infrastructure/Services/Mediator/ServiceProviderExtensions.cs
  • src/Web.Api.Tests/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandlerTests.cs
  • src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandler.cs
  • src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionRequest.cs
  • src/Web.Api/Features/Currencies/CurrenciesCollectionController.cs
  • src/Web.Api/Features/Currencies/DeleteCurrenciesCollection/DeleteCurrenciesCollectionHandler.cs
  • src/Web.Api/Features/Currencies/DeleteCurrenciesCollection/DeleteCurrenciesCollectionRequest.cs
  • src/Web.Api/Features/Currencies/Dtos/CurrenciesCollectionDto.cs
  • src/Web.Api/Features/Currencies/Dtos/CurrencyItemDto.cs
  • src/Web.Api/Features/Currencies/Dtos/WeeklyCurrencyChartDataDto.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataHandler.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataQueryParams.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataResponse.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesCollection/GetCurrenciesCollectionHandler.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesCollection/GetCurrenciesCollectionQueryParams.cs
🧰 Additional context used
📓 Path-based instructions (3)
**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.cs: Always follow the linter rules defined in src/standard.ruleset
Use PascalCase for namespaces (e.g., TechInterviewer.Features.Interviews)
Use PascalCase for class names (e.g., InterviewService)
Use PascalCase with "I" prefix for interface names (e.g., IInterviewRepository)
Use PascalCase for method names (e.g., GetInterviewByIdAsync)
Use PascalCase for properties (e.g., CreatedAt)
Use camelCase with underscore prefix for private fields (e.g., _dbContext)
Use camelCase for local variables
Use camelCase for method parameters (e.g., cancellationToken)
Use PascalCase with "Async" suffix for async methods (e.g., CreateInterviewAsync)
Use async/await for all asynchronous operations

Files:

  • src/Web.Api/Features/Currencies/DeleteCurrenciesCollection/DeleteCurrenciesCollectionRequest.cs
  • src/Web.Api.Tests/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandlerTests.cs
  • src/Infrastructure/Services/Mediator/ServiceProviderExtensions.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataHandler.cs
  • src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandler.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesCollection/GetCurrenciesCollectionHandler.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesCollection/GetCurrenciesCollectionQueryParams.cs
  • src/Web.Api/Features/Currencies/Dtos/WeeklyCurrencyChartDataDto.cs
  • src/Web.Api/Features/Currencies/CurrenciesCollectionController.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataResponse.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataQueryParams.cs
  • src/Web.Api/Features/Currencies/Dtos/CurrenciesCollectionDto.cs
  • src/Web.Api/Features/Currencies/Dtos/CurrencyItemDto.cs
  • src/Web.Api/Features/Currencies/DeleteCurrenciesCollection/DeleteCurrenciesCollectionHandler.cs
  • src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionRequest.cs

⚙️ CodeRabbit configuration file

**/*.cs: Review C# code for:

  • SOLID principles and clean architecture adherence
  • Proper async/await usage and avoiding async void
  • Null safety and proper use of nullable reference types
  • Dependency injection best practices
  • Exception handling patterns
  • Memory management and IDisposable implementation
  • LINQ query optimization

Files:

  • src/Web.Api/Features/Currencies/DeleteCurrenciesCollection/DeleteCurrenciesCollectionRequest.cs
  • src/Web.Api.Tests/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandlerTests.cs
  • src/Infrastructure/Services/Mediator/ServiceProviderExtensions.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataHandler.cs
  • src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandler.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesCollection/GetCurrenciesCollectionHandler.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesCollection/GetCurrenciesCollectionQueryParams.cs
  • src/Web.Api/Features/Currencies/Dtos/WeeklyCurrencyChartDataDto.cs
  • src/Web.Api/Features/Currencies/CurrenciesCollectionController.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataResponse.cs
  • src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataQueryParams.cs
  • src/Web.Api/Features/Currencies/Dtos/CurrenciesCollectionDto.cs
  • src/Web.Api/Features/Currencies/Dtos/CurrencyItemDto.cs
  • src/Web.Api/Features/Currencies/DeleteCurrenciesCollection/DeleteCurrenciesCollectionHandler.cs
  • src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionRequest.cs
**/*Tests.cs

⚙️ CodeRabbit configuration file

**/*Tests.cs: Review test files for:

  • Adequate test coverage
  • Proper use of xUnit/NUnit/MSTest assertions
  • Mock objects with Moq or NSubstitute
  • Arrange-Act-Assert pattern
  • Integration test isolation

Files:

  • src/Web.Api.Tests/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandlerTests.cs
**/Services/**/*.cs

⚙️ CodeRabbit configuration file

**/Services/**/*.cs: Review service layer for:

  • Single responsibility principle
  • Proper abstraction through interfaces
  • Transaction management
  • Business logic encapsulation

Files:

  • src/Infrastructure/Services/Mediator/ServiceProviderExtensions.cs
🧬 Code graph analysis (7)
src/Web.Api.Tests/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandlerTests.cs (1)
src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandler.cs (2)
  • Task (18-51)
  • CreateCurrenciesCollectionHandler (14-52)
src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataHandler.cs (3)
src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataResponse.cs (1)
  • GetCurrenciesChartDataResponse (15-23)
src/Infrastructure/Services/Mediator/ServiceProviderExtensions.cs (1)
  • Task (7-15)
src/Web.Api/Features/Currencies/Dtos/WeeklyCurrencyChartDataDto.cs (1)
  • WeeklyCurrencyChartDataDto (14-22)
src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandler.cs (2)
src/Web.Api/Features/Currencies/Dtos/CurrenciesCollectionDto.cs (2)
  • CurrenciesCollectionDto (20-22)
  • CurrenciesCollectionDto (24-37)
src/Web.Api/Features/Currencies/DeleteCurrenciesCollection/DeleteCurrenciesCollectionHandler.cs (1)
  • Task (18-29)
src/Web.Api/Features/Currencies/GetCurrenciesCollection/GetCurrenciesCollectionHandler.cs (3)
src/Domain/ValueObjects/Pagination/Pageable.cs (1)
  • Pageable (10-20)
src/Web.Api/Features/Currencies/Dtos/CurrenciesCollectionDto.cs (2)
  • CurrenciesCollectionDto (20-22)
  • CurrenciesCollectionDto (24-37)
src/Infrastructure/Services/Mediator/ServiceProviderExtensions.cs (1)
  • Task (7-15)
src/Web.Api/Features/Currencies/Dtos/WeeklyCurrencyChartDataDto.cs (1)
src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataHandler.cs (1)
  • DateTime (69-73)
src/Web.Api/Features/Currencies/CurrenciesCollectionController.cs (8)
src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandler.cs (2)
  • Task (18-51)
  • CreateCurrenciesCollectionHandler (14-52)
src/Web.Api/Features/Currencies/DeleteCurrenciesCollection/DeleteCurrenciesCollectionHandler.cs (3)
  • Task (18-29)
  • DeleteCurrenciesCollectionHandler (8-30)
  • DeleteCurrenciesCollectionHandler (13-16)
src/Infrastructure/Services/Mediator/ServiceProviderExtensions.cs (1)
  • Task (7-15)
src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataHandler.cs (3)
  • Task (24-67)
  • GetCurrenciesChartDataHandler (14-74)
  • GetCurrenciesChartDataHandler (19-22)
src/Web.Api/Features/Currencies/GetCurrenciesCollection/GetCurrenciesCollectionHandler.cs (3)
  • Task (22-33)
  • GetCurrenciesCollectionHandler (12-34)
  • GetCurrenciesCollectionHandler (17-20)
src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataResponse.cs (1)
  • GetCurrenciesChartDataResponse (15-23)
src/Domain/ValueObjects/Pagination/Pageable.cs (1)
  • Pageable (10-20)
src/Web.Api/Features/Currencies/Dtos/CurrenciesCollectionDto.cs (2)
  • CurrenciesCollectionDto (20-22)
  • CurrenciesCollectionDto (24-37)
src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataResponse.cs (2)
src/Web.Api/Features/Currencies/Dtos/WeeklyCurrencyChartDataDto.cs (1)
  • WeeklyCurrencyChartDataDto (14-22)
src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataHandler.cs (1)
  • DateTime (69-73)
🔇 Additional comments (11)
src/Web.Api/Features/Currencies/GetCurrenciesCollection/GetCurrenciesCollectionQueryParams.cs (1)

5-7: LGTM!

Clean design leveraging PageModel for pagination properties. The empty body is appropriate when all necessary query parameters are inherited from the base class.

src/Infrastructure/Services/Mediator/ServiceProviderExtensions.cs (1)

11-11: LGTM!

Simplifying the constraint from the fully qualified Infrastructure.Services.Mediator.IRequestHandler<TRequest, TResult> to IRequestHandler<TRequest, TResult> improves readability without changing functionality. Since the interface is in the same namespace, the simple name is clearer.

src/Web.Api/Features/Currencies/Dtos/CurrencyItemDto.cs (1)

7-7: > Likely an incorrect or invalid review comment.

src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataHandler.cs (2)

69-73: LGTM: Week calculation logic is correct.

The GetStartOfWeek helper correctly computes Monday-based week boundaries using modulo arithmetic.


46-46: Remove null safety check suggestion—the Currencies property is non-nullable.

The Currencies property in CurrenciesCollection is defined as Dictionary<Currency, double>, not Dictionary<Currency, double>?. It is guaranteed to never be null by the type system and is always initialized in the constructor. No additional null check is required.

Likely an incorrect or invalid review comment.

src/Web.Api.Tests/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandlerTests.cs (1)

16-57: Well-structured test with good coverage of the success path.

The test follows the AAA pattern correctly and validates both the returned DTO and the persisted entity state. Good use of await using for proper disposal.

src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandler.cs (2)

14-16: Primary constructor is clean and follows the codebase pattern.

The handler correctly implements IRequestHandler and injects DatabaseContext via primary constructor.


41-45: Entity creation logic is correct.

The transformation from request DTOs to domain entities via CurrencyContent and CurrenciesCollection follows proper patterns.

src/Web.Api/Features/Currencies/GetCurrenciesCollection/GetCurrenciesCollectionHandler.cs (1)

22-33: Clean pagination implementation.

Good use of AsNoTracking() for read-only queries and proper projection within AsPaginatedAsync. The descending order by CurrencyDate provides a sensible default for displaying recent data first.

src/Web.Api/Features/Currencies/Dtos/CurrenciesCollectionDto.cs (1)

24-37: Mapping constructor is well implemented.

The entity-to-DTO mapping logic is clean and correctly transforms the dictionary-based currencies into a list of CurrencyItemDto.

src/Web.Api/Features/Currencies/CurrenciesCollectionController.cs (1)

29-37: Public chart endpoint looks good.

The GetChartData endpoint is correctly public (no role restriction) with null-coalescing for query params.

Comment on lines +32 to +48
var existingRecord = await context.CurrencyCollections
.AsNoTracking()
.FirstOrDefaultAsync(x => x.CurrencyDate == request.CurrencyDate, cancellationToken);

if (existingRecord != null)
{
throw new BadRequestException($"A currency collection for date {request.CurrencyDate:yyyy-MM-dd} already exists.");
}

var currencyContents = request.Currencies
.Select(x => new CurrencyContent(x.Value, x.Key, request.CurrencyDate))
.ToDictionary(x => x.Currency, x => x);

var entity = new CurrenciesCollection(currencyContents);

context.CurrencyCollections.Add(entity);
await context.TrySaveChangesAsync(cancellationToken);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for CurrencyDate in entity configuration files
find . -type f -name "*.cs" | xargs grep -l "CurrenciesCollection\|CurrencyCollection" | head -20

Repository: Techinterview-space/web-api

Length of output: 1473


🏁 Script executed:

# Search for unique constraints or indexes on CurrencyDate
rg -n "CurrencyDate" --type=cs -A 5 -B 5

Repository: Techinterview-space/web-api

Length of output: 36893


🏁 Script executed:

# Search for DbUpdateException handling in the handler file
rg -n "DbUpdateException\|catch\|TrySaveChangesAsync" --type=cs src/Web.Api/Features/Currencies/

Repository: Techinterview-space/web-api

Length of output: 53


🏁 Script executed:

# Find TrySaveChangesAsync implementation
rg -n "TrySaveChangesAsync" --type=cs -A 20

Repository: Techinterview-space/web-api

Length of output: 50383


🏁 Script executed:

# Check DatabaseContext file for this method
cat src/Infrastructure/Database/DatabaseContext.cs

Repository: Techinterview-space/web-api

Length of output: 7373


🏁 Script executed:

# Check if there's any special handling for DbUpdateException in the handler or related code
rg -n "DbUpdateException" --type=cs src/

Repository: Techinterview-space/web-api

Length of output: 53


🏁 Script executed:

# Check if TrySaveChangesAsync is overridden or if there's custom exception handling elsewhere
rg -n "catch.*DbUpdateException\|DatabaseException" --type=cs -A 3

Repository: Techinterview-space/web-api

Length of output: 53


Race condition exists despite unique constraint—proper exception handling is missing.

A unique constraint on CurrencyDate already exists in the database schema and entity configuration, so the TOCTOU window is partially mitigated. However, when concurrent requests violate this constraint, TrySaveChangesAsync catches the resulting DbUpdateException and wraps it generically in a DatabaseException, rather than converting it to a user-friendly BadRequestException.

Modify TrySaveChangesAsync to specifically catch DbUpdateException (including the unique constraint violation) and convert it to BadRequestException with a descriptive message for the user.

🤖 Prompt for AI Agents
In
src/Web.Api/Features/Currencies/CreateCurrenciesCollection/CreateCurrenciesCollectionHandler.cs
around lines 32 to 48, the handler checks for an existing CurrencyCollection but
relies on TrySaveChangesAsync to handle concurrent inserts; update
TrySaveChangesAsync to specifically catch DbUpdateException (and inspect the
inner/database exception or SQL error code for unique constraint violation on
CurrencyDate) and throw a BadRequestException with a descriptive message like "A
currency collection for that date already exists." for that case; for other
DbUpdateExceptions rethrow or wrap as DatabaseException as before, and ensure
the handler no longer expects only the pre-check to prevent duplicate inserts.

Comment on lines +50 to +59
[HttpPost("")]
[HasAnyRole(Role.Admin)]
public Task<CurrenciesCollectionDto> Create(
[FromBody] CreateCurrenciesCollectionRequest request,
CancellationToken cancellationToken)
{
return _serviceProvider.HandleBy<CreateCurrenciesCollectionHandler, CreateCurrenciesCollectionRequest, CurrenciesCollectionDto>(
request,
cancellationToken);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add null validation for the request body.

If a client sends a request without a body or with null, this will pass null to the handler. While the handler validates request.Currencies, accessing request.Currencies when request itself is null will throw a NullReferenceException before the validation logic runs.

🔎 Proposed fix
     [HttpPost("")]
     [HasAnyRole(Role.Admin)]
     public Task<CurrenciesCollectionDto> Create(
         [FromBody] CreateCurrenciesCollectionRequest request,
         CancellationToken cancellationToken)
     {
+        ArgumentNullException.ThrowIfNull(request);
         return _serviceProvider.HandleBy<CreateCurrenciesCollectionHandler, CreateCurrenciesCollectionRequest, CurrenciesCollectionDto>(
             request,
             cancellationToken);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[HttpPost("")]
[HasAnyRole(Role.Admin)]
public Task<CurrenciesCollectionDto> Create(
[FromBody] CreateCurrenciesCollectionRequest request,
CancellationToken cancellationToken)
{
return _serviceProvider.HandleBy<CreateCurrenciesCollectionHandler, CreateCurrenciesCollectionRequest, CurrenciesCollectionDto>(
request,
cancellationToken);
}
[HttpPost("")]
[HasAnyRole(Role.Admin)]
public Task<CurrenciesCollectionDto> Create(
[FromBody] CreateCurrenciesCollectionRequest request,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
return _serviceProvider.HandleBy<CreateCurrenciesCollectionHandler, CreateCurrenciesCollectionRequest, CurrenciesCollectionDto>(
request,
cancellationToken);
}
🤖 Prompt for AI Agents
In src/Web.Api/Features/Currencies/CurrenciesCollectionController.cs around
lines 50-59, add null validation for the incoming request body: change the
method return type from Task<CurrenciesCollectionDto> to
Task<ActionResult<CurrenciesCollectionDto>> (or Task<IActionResult>), check if
request is null at the start and return BadRequest("Request body is required")
(or a suitable ModelState error) before calling the handler, and then return the
handler result wrapped in Ok(...) so the handler is never invoked with a null
request.

Comment on lines +18 to +29
public async Task<bool> Handle(
DeleteCurrenciesCollectionRequest request,
CancellationToken cancellationToken)
{
var entity = await _context.CurrencyCollections
.ByIdOrFailAsync(request.Id, cancellationToken: cancellationToken);

_context.CurrencyCollections.Remove(entity);
await _context.TrySaveChangesAsync(cancellationToken);

return true;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Misleading return type: always returns true.

The method always returns true, making the bool return type misleading. Since ByIdOrFailAsync throws an exception on failure, the boolean serves no purpose.

Consider either:

  1. Changing the return type to Task (no return value)
  2. Returning meaningful success/failure information if there are scenarios where deletion might fail without throwing
🔎 Proposed refactor to remove unnecessary return type
-public class DeleteCurrenciesCollectionHandler
-    : IRequestHandler<DeleteCurrenciesCollectionRequest, bool>
+public class DeleteCurrenciesCollectionHandler
+    : IRequestHandler<DeleteCurrenciesCollectionRequest, Unit>
 {
     private readonly DatabaseContext _context;
 
     public DeleteCurrenciesCollectionHandler(DatabaseContext context)
     {
         _context = context;
     }
 
-    public async Task<bool> Handle(
+    public async Task<Unit> Handle(
         DeleteCurrenciesCollectionRequest request,
         CancellationToken cancellationToken)
     {
         var entity = await _context.CurrencyCollections
             .ByIdOrFailAsync(request.Id, cancellationToken: cancellationToken);
 
         _context.CurrencyCollections.Remove(entity);
         await _context.TrySaveChangesAsync(cancellationToken);
 
-        return true;
+        return Unit.Value;
     }
 }

Note: If Unit is not available in your mediator library, you could use an empty response type or keep bool but document the behavior clearly.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/Web.Api/Features/Currencies/DeleteCurrenciesCollection/DeleteCurrenciesCollectionHandler.cs
around lines 18 to 29, the handler method always returns true making the bool
return type misleading; change the signature to return Task (void async) or the
mediator's Unit type and remove the return statement, or alternatively implement
a meaningful boolean result if non-throwing failure paths are possible; update
any callers or interface definitions accordingly to match the new return type.

Comment on lines +5 to +8
public record DeleteCurrenciesCollectionRequest
{
public Guid Id { get; init; }
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find the handler implementation
fd "DeleteCurrenciesCollectionHandler" -e .cs

Repository: Techinterview-space/web-api

Length of output: 166


🏁 Script executed:

# Search for handler class definition and its Handle method
rg -n "class DeleteCurrenciesCollectionHandler" -A 20 --type=cs

Repository: Techinterview-space/web-api

Length of output: 2831


🏁 Script executed:

# Search for any validator for DeleteCurrenciesCollectionRequest
rg -n "DeleteCurrenciesCollectionRequest" --type=cs -A 5 | head -100

Repository: Techinterview-space/web-api

Length of output: 2738


🏁 Script executed:

# Search for controller that might use this request
rg -n "DeleteCurrenciesCollection" --type=cs -g "*Controller.cs" -A 15

Repository: Techinterview-space/web-api

Length of output: 2281


🏁 Script executed:

# Get the complete controller action (around line 60-70)
rg -n -B 10 "HandleBy<DeleteCurrenciesCollectionHandler" src/Web.Api/Features/Currencies/CurrenciesCollectionController.cs

Repository: Techinterview-space/web-api

Length of output: 450


🏁 Script executed:

# Search for ByIdOrFailAsync implementation
rg -n "ByIdOrFailAsync" --type=cs -A 10 | head -50

Repository: Techinterview-space/web-api

Length of output: 3774


🏁 Script executed:

# Search for validators for DeleteCurrenciesCollectionRequest
rg -n "DeleteCurrenciesCollectionRequest" --type=cs -g "*Validator*"

Repository: Techinterview-space/web-api

Length of output: 53


🏁 Script executed:

# Broader search for validator classes related to Currencies or DeleteCurrenciesCollection
fd -e .cs -path "*Validator*" | xargs rg -l "DeleteCurrenciesCollectionRequest"

Repository: Techinterview-space/web-api

Length of output: 514


🏁 Script executed:

# Search for ByIdOrFailAsync definition
rg -n "ByIdOrFailAsync" --type=cs -B 2 -A 15 | grep -A 15 "public.*ByIdOrFailAsync"

Repository: Techinterview-space/web-api

Length of output: 2560


🏁 Script executed:

# Search more broadly for this method
rg -n "ByIdOrFailAsync" --type=cs

Repository: Techinterview-space/web-api

Length of output: 4261


🏁 Script executed:

# Look for extension method files
fd -e .cs -path "*Extension*" -o -path "*Helper*"

Repository: Techinterview-space/web-api

Length of output: 239


Add explicit validation to reject empty GUIDs in the request handler.

The ByIdOrFailAsync method has no explicit guard against Guid.Empty. While it will eventually throw NotFoundException if no entity exists with that ID, this relies on implicit validation. Explicitly validate that request.Id != Guid.Empty in the handler before querying the database, or add a validator to the request to reject empty GUIDs upfront.

🤖 Prompt for AI Agents
In
src/Web.Api/Features/Currencies/DeleteCurrenciesCollection/DeleteCurrenciesCollectionRequest.cs
around lines 5 to 8, the request allows Guid.Empty which defers failure to the
repository; add explicit validation to reject empty GUIDs by either (1) adding a
validator for DeleteCurrenciesCollectionRequest that fails when Id ==
Guid.Empty, or (2) adding a guard in the handler that checks request.Id !=
Guid.Empty and returns a BadRequest/throws a ValidationException before calling
ByIdOrFailAsync; implement one of these approaches so empty GUIDs are rejected
up front with a clear error.

{
public Currency Currency { get; init; }

public double Value { get; init; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use decimal instead of double for currency values.

The double type uses binary floating-point representation, which introduces precision errors in decimal arithmetic. For financial data, use decimal to ensure exact representations and avoid rounding issues.

🔎 Recommended fix
-    public double Value { get; init; }
+    public decimal Value { get; init; }

Ensure that all related code handling currency values also uses decimal:

  • Database columns should be DECIMAL/NUMERIC types
  • API contracts should specify decimal precision
  • Any calculations should use decimal arithmetic
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public double Value { get; init; }
public decimal Value { get; init; }
🤖 Prompt for AI Agents
In src/Web.Api/Features/Currencies/Dtos/CurrencyItemDto.cs around line 9, the
DTO uses a double for currency Value which can introduce precision errors;
change the property type to decimal and update any related code to use decimal
arithmetic and mapping, ensure database columns are DECIMAL/NUMERIC and API
contracts/documentation reflect decimal precision, and update
serializers/mappers/tests that touch this property to handle decimal.

Comment on lines +14 to +22
public WeeklyCurrencyChartDataDto(
DateTime weekStartDate,
DateTime weekEndDate,
List<CurrencyItemDto> averageCurrencies)
{
WeekStartDate = weekStartDate;
WeekEndDate = weekEndDate;
AverageCurrencies = averageCurrencies;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add null validation for constructor parameters.

The constructor doesn't validate that averageCurrencies is non-null. Consider adding an ArgumentNullException check or using nullable reference types to make the contract explicit.

🔎 Proposed fix with null validation
 public WeeklyCurrencyChartDataDto(
     DateTime weekStartDate,
     DateTime weekEndDate,
     List<CurrencyItemDto> averageCurrencies)
 {
+    ArgumentNullException.ThrowIfNull(averageCurrencies);
     WeekStartDate = weekStartDate;
     WeekEndDate = weekEndDate;
     AverageCurrencies = averageCurrencies;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public WeeklyCurrencyChartDataDto(
DateTime weekStartDate,
DateTime weekEndDate,
List<CurrencyItemDto> averageCurrencies)
{
WeekStartDate = weekStartDate;
WeekEndDate = weekEndDate;
AverageCurrencies = averageCurrencies;
}
public WeeklyCurrencyChartDataDto(
DateTime weekStartDate,
DateTime weekEndDate,
List<CurrencyItemDto> averageCurrencies)
{
ArgumentNullException.ThrowIfNull(averageCurrencies);
WeekStartDate = weekStartDate;
WeekEndDate = weekEndDate;
AverageCurrencies = averageCurrencies;
}
🤖 Prompt for AI Agents
In src/Web.Api/Features/Currencies/Dtos/WeeklyCurrencyChartDataDto.cs around
lines 14 to 22, the constructor does not validate that the averageCurrencies
parameter is non-null; add a guard that throws ArgumentNullException when
averageCurrencies is null (e.g., if (averageCurrencies == null) throw new
ArgumentNullException(nameof(averageCurrencies))); then assign the parameters as
before so the DTO enforces a non-null list contract.

Comment on lines +15 to +23
public GetCurrenciesChartDataResponse(
List<WeeklyCurrencyChartDataDto> weeklyData,
DateTime fromDate,
DateTime toDate)
{
WeeklyData = weeklyData;
FromDate = fromDate;
ToDate = toDate;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add null validation for constructor parameters.

The constructor doesn't validate that weeklyData is non-null. Consider adding validation to ensure the contract is explicit and prevent NullReferenceException downstream.

🔎 Proposed fix with null validation
 public GetCurrenciesChartDataResponse(
     List<WeeklyCurrencyChartDataDto> weeklyData,
     DateTime fromDate,
     DateTime toDate)
 {
+    ArgumentNullException.ThrowIfNull(weeklyData);
     WeeklyData = weeklyData;
     FromDate = fromDate;
     ToDate = toDate;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public GetCurrenciesChartDataResponse(
List<WeeklyCurrencyChartDataDto> weeklyData,
DateTime fromDate,
DateTime toDate)
{
WeeklyData = weeklyData;
FromDate = fromDate;
ToDate = toDate;
}
public GetCurrenciesChartDataResponse(
List<WeeklyCurrencyChartDataDto> weeklyData,
DateTime fromDate,
DateTime toDate)
{
ArgumentNullException.ThrowIfNull(weeklyData);
WeeklyData = weeklyData;
FromDate = fromDate;
ToDate = toDate;
}
🤖 Prompt for AI Agents
In
src/Web.Api/Features/Currencies/GetCurrenciesChartData/GetCurrenciesChartDataResponse.cs
around lines 15-23, the constructor assigns WeeklyData, FromDate and ToDate but
does not validate that weeklyData is non-null; add a null check for weeklyData
and throw an ArgumentNullException (with parameter name) if null before
assigning, ensuring the contract is explicit and prevents downstream
NullReferenceExceptions.

@maximgorbatyuk
maximgorbatyuk deleted the currencies-crud branch January 4, 2026 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant