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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public static async Task<TResult> HandleBy<THandler, TRequest, TResult>(
this IServiceProvider serviceProvider,
TRequest request,
CancellationToken cancellationToken = default)
where THandler : Infrastructure.Services.Mediator.IRequestHandler<TRequest, TResult>
where THandler : IRequestHandler<TRequest, TResult>
{
var handler = serviceProvider.GetRequiredService<THandler>();
return await handler.Handle(request, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Domain.Entities.Salaries;
using Domain.Validation.Exceptions;
using Microsoft.EntityFrameworkCore;
using TestUtils.Db;
using TestUtils.Fakes;
using Web.Api.Features.Currencies.CreateCurrenciesCollection;
using Xunit;

namespace Web.Api.Tests.Features.Currencies.CreateCurrenciesCollection;

public class CreateCurrenciesCollectionHandlerTests
{
[Fact]
public async Task Handle_NoExistingRecordForDate_ValidData_CreatesNewRecord()
{
// Arrange
await using var context = new InMemoryDatabaseContext();
var handler = new CreateCurrenciesCollectionHandler(context);

var currencyDate = new DateTime(2024, 1, 15);
var request = new CreateCurrenciesCollectionRequest
{
CurrencyDate = currencyDate,
Currencies = new Dictionary<Currency, double>
{
{ Currency.USD, 450.50 },
{ Currency.EUR, 520.75 },
{ Currency.KZT, 1.0 }
}
};

Assert.Equal(0, context.CurrencyCollections.Count());

// Act
var result = await handler.Handle(request, CancellationToken.None);

// Assert
Assert.NotNull(result);
Assert.Equal(currencyDate, result.CurrencyDate);
Assert.Equal(3, result.Currencies.Count);

var createdRecord = await context.CurrencyCollections
.FirstOrDefaultAsync(x => x.CurrencyDate == currencyDate);

Assert.NotNull(createdRecord);
Assert.Equal(currencyDate, createdRecord.CurrencyDate);
Assert.Equal(3, createdRecord.Currencies.Count);
Assert.Equal(450.50, createdRecord.Currencies[Currency.USD]);
Assert.Equal(520.75, createdRecord.Currencies[Currency.EUR]);
Assert.Equal(1.0, createdRecord.Currencies[Currency.KZT]);
Assert.Equal(1, context.CurrencyCollections.Count());
}

[Fact]
public async Task Handle_ExistingRecordForDate_ValidData_ThrowsBadRequestException()
{
// Arrange
await using var context = new InMemoryDatabaseContext();
var currencyDate = new DateTime(2024, 1, 15);

var existingRecord = new CurrenciesCollectionFake(currencyDate).Please(context);

var handler = new CreateCurrenciesCollectionHandler(context);

var request = new CreateCurrenciesCollectionRequest
{
CurrencyDate = currencyDate,
Currencies = new Dictionary<Currency, double>
{
{ Currency.USD, 460.00 },
{ Currency.EUR, 530.00 }
}
};

Assert.Equal(1, context.CurrencyCollections.Count());

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

Assert.Contains("already exists", exception.Message);

var allRecords = context.CurrencyCollections.ToList();
Assert.Single(allRecords);
Assert.Equal(existingRecord.Id, allRecords[0].Id);
}

[Fact]
public async Task Handle_NoCurrencies_ThrowsBadRequestException()
{
// Arrange
await using var context = new InMemoryDatabaseContext();
var handler = new CreateCurrenciesCollectionHandler(context);

var request = new CreateCurrenciesCollectionRequest
{
CurrencyDate = new DateTime(2024, 1, 15),
Currencies = new Dictionary<Currency, double>()
};

Assert.Equal(0, context.CurrencyCollections.Count());

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

Assert.Contains("at least one currency", exception.Message);
Assert.Equal(0, context.CurrencyCollections.Count());
}

[Fact]
public async Task Handle_FutureDate_ThrowsBadRequestException()
{
// Arrange
await using var context = new InMemoryDatabaseContext();
var handler = new CreateCurrenciesCollectionHandler(context);

var futureDate = DateTime.UtcNow.Date.AddDays(30);
var request = new CreateCurrenciesCollectionRequest
{
CurrencyDate = futureDate,
Currencies = new Dictionary<Currency, double>
{
{ Currency.USD, 450.00 },
{ Currency.EUR, 520.00 }
}
};

Assert.Equal(0, context.CurrencyCollections.Count());

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

Assert.Contains("future", exception.Message, StringComparison.OrdinalIgnoreCase);
Assert.Equal(0, context.CurrencyCollections.Count());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Domain.Entities.Currencies;
using Domain.Validation.Exceptions;
using Infrastructure.Database;
using Infrastructure.Services.Mediator;
using Microsoft.EntityFrameworkCore;
using Web.Api.Features.Currencies.Dtos;

namespace Web.Api.Features.Currencies.CreateCurrenciesCollection;

public class CreateCurrenciesCollectionHandler(
DatabaseContext context)
: IRequestHandler<CreateCurrenciesCollectionRequest, CurrenciesCollectionDto>
{
public async Task<CurrenciesCollectionDto> Handle(
CreateCurrenciesCollectionRequest request,
CancellationToken cancellationToken)
{
if (request.Currencies == null || request.Currencies.Count == 0)
{
throw new BadRequestException("Currencies collection must have at least one currency.");
}

if (request.CurrencyDate > System.DateTime.UtcNow.Date)
{
throw new BadRequestException("Currency date cannot be in the future.");
}

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);
Comment on lines +32 to +48

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.


return new CurrenciesCollectionDto(entity);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Domain.Entities.Salaries;

namespace Web.Api.Features.Currencies.CreateCurrenciesCollection;

public record CreateCurrenciesCollectionRequest
{
[Required]
public Dictionary<Currency, double> Currencies { get; init; }

[Required]
public DateTime CurrencyDate { get; init; }
}
71 changes: 71 additions & 0 deletions src/Web.Api/Features/Currencies/CurrenciesCollectionController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Domain.Enums;
using Domain.ValueObjects.Pagination;
using Infrastructure.Services.Mediator;
using Microsoft.AspNetCore.Mvc;
using Web.Api.Features.Currencies.CreateCurrenciesCollection;
using Web.Api.Features.Currencies.DeleteCurrenciesCollection;
using Web.Api.Features.Currencies.Dtos;
using Web.Api.Features.Currencies.GetCurrenciesChartData;
using Web.Api.Features.Currencies.GetCurrenciesCollection;
using Web.Api.Setup.Attributes;

namespace Web.Api.Features.Currencies;

[ApiController]
[Route("api/currencies")]
public class CurrenciesCollectionController : ControllerBase
{
private readonly IServiceProvider _serviceProvider;

public CurrenciesCollectionController(
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}

[HttpGet("chart")]
public Task<GetCurrenciesChartDataResponse> GetChartData(
[FromQuery] GetCurrenciesChartDataQueryParams queryParams,
CancellationToken cancellationToken)
{
return _serviceProvider.HandleBy<GetCurrenciesChartDataHandler, GetCurrenciesChartDataQueryParams, GetCurrenciesChartDataResponse>(
queryParams ?? new GetCurrenciesChartDataQueryParams(),
cancellationToken);
}

[HttpGet("")]
[HasAnyRole(Role.Admin)]
public Task<Pageable<CurrenciesCollectionDto>> GetAll(
[FromQuery] GetCurrenciesCollectionQueryParams queryParams,
CancellationToken cancellationToken)
{
return _serviceProvider.HandleBy<GetCurrenciesCollectionHandler, GetCurrenciesCollectionQueryParams, Pageable<CurrenciesCollectionDto>>(
queryParams ?? new GetCurrenciesCollectionQueryParams(),
cancellationToken);
}

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

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.


[HttpDelete("{id:guid}")]
[HasAnyRole(Role.Admin)]
public Task<bool> Delete(
[FromRoute] Guid id,
CancellationToken cancellationToken)
{
return _serviceProvider.HandleBy<DeleteCurrenciesCollectionHandler, DeleteCurrenciesCollectionRequest, bool>(
new DeleteCurrenciesCollectionRequest { Id = id },
cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Threading;
using System.Threading.Tasks;
using Infrastructure.Database;
using Infrastructure.Services.Mediator;

namespace Web.Api.Features.Currencies.DeleteCurrenciesCollection;

public class DeleteCurrenciesCollectionHandler
: IRequestHandler<DeleteCurrenciesCollectionRequest, bool>
{
private readonly DatabaseContext _context;

public DeleteCurrenciesCollectionHandler(DatabaseContext context)
{
_context = context;
}

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;
}
Comment on lines +18 to +29

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.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace Web.Api.Features.Currencies.DeleteCurrenciesCollection;

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

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.

38 changes: 38 additions & 0 deletions src/Web.Api/Features/Currencies/Dtos/CurrenciesCollectionDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Domain.Entities.Currencies;

namespace Web.Api.Features.Currencies.Dtos;

public record CurrenciesCollectionDto
{
public Guid Id { get; init; }

public List<CurrencyItemDto> Currencies { get; init; }

public DateTime CurrencyDate { get; init; }

public DateTimeOffset CreatedAt { get; init; }

public DateTimeOffset UpdatedAt { get; init; }

public CurrenciesCollectionDto()
{
}

public CurrenciesCollectionDto(CurrenciesCollection entity)
{
Id = entity.Id;
CurrencyDate = entity.CurrencyDate;
CreatedAt = entity.CreatedAt;
UpdatedAt = entity.UpdatedAt;
Currencies = entity.Currencies
.Select(x => new CurrencyItemDto
{
Currency = x.Key,
Value = x.Value,
})
.ToList();
}
}
10 changes: 10 additions & 0 deletions src/Web.Api/Features/Currencies/Dtos/CurrencyItemDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Domain.Entities.Salaries;

namespace Web.Api.Features.Currencies.Dtos;

public record CurrencyItemDto
{
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.

}
Loading