Repository navigation
Added classes for CRD and chart #159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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); | ||
|
|
||
| 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; } | ||
| } |
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add null validation for the request body. If a client sends a request without a body or with 🔎 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| [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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Consider either:
🔎 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
🤖 Prompt for AI Agents |
||
| } | ||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Find the handler implementation
fd "DeleteCurrenciesCollectionHandler" -e .csRepository: 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=csRepository: Techinterview-space/web-api Length of output: 2831 🏁 Script executed: # Search for any validator for DeleteCurrenciesCollectionRequest
rg -n "DeleteCurrenciesCollectionRequest" --type=cs -A 5 | head -100Repository: 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 15Repository: 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.csRepository: Techinterview-space/web-api Length of output: 450 🏁 Script executed: # Search for ByIdOrFailAsync implementation
rg -n "ByIdOrFailAsync" --type=cs -A 10 | head -50Repository: 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=csRepository: 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 🤖 Prompt for AI Agents |
||
| 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(); | ||
| } | ||
| } |
| 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; } | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use The 🔎 Recommended fix- public double Value { get; init; }
+ public decimal Value { get; init; }Ensure that all related code handling currency values also uses
📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: Techinterview-space/web-api
Length of output: 1473
🏁 Script executed:
Repository: Techinterview-space/web-api
Length of output: 36893
🏁 Script executed:
Repository: Techinterview-space/web-api
Length of output: 53
🏁 Script executed:
Repository: Techinterview-space/web-api
Length of output: 50383
🏁 Script executed:
# Check DatabaseContext file for this method cat src/Infrastructure/Database/DatabaseContext.csRepository: Techinterview-space/web-api
Length of output: 7373
🏁 Script executed:
Repository: Techinterview-space/web-api
Length of output: 53
🏁 Script executed:
Repository: Techinterview-space/web-api
Length of output: 53
Race condition exists despite unique constraint—proper exception handling is missing.
A unique constraint on
CurrencyDatealready exists in the database schema and entity configuration, so the TOCTOU window is partially mitigated. However, when concurrent requests violate this constraint,TrySaveChangesAsynccatches the resultingDbUpdateExceptionand wraps it generically in aDatabaseException, rather than converting it to a user-friendlyBadRequestException.Modify
TrySaveChangesAsyncto specifically catchDbUpdateException(including the unique constraint violation) and convert it toBadRequestExceptionwith a descriptive message for the user.🤖 Prompt for AI Agents