add currency converter service and integrated it with vehicle enrollment management
This commit is contained in:
parent
5ee8c9c5df
commit
b1aceac750
@ -0,0 +1,12 @@
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
|
||||
public interface CurrencyConverterService
|
||||
{
|
||||
Task<decimal> ConvertAsync(decimal amount, Currency from, Currency to,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task<decimal> ConvertAsync(decimal amount, Currency from, Currency to,
|
||||
DateTimeOffset time, CancellationToken cancellationToken);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
|
||||
public interface SessionCurrencyService
|
||||
{
|
||||
public Currency Currency { get; }
|
||||
}
|
@ -2,6 +2,8 @@ using MediatR;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
|
||||
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||
using AutoMapper;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Queries.GetVehicleEnrollment;
|
||||
@ -12,12 +14,19 @@ public class GetVehicleEnrollmentQueryHandler :
|
||||
private readonly UnitOfWork _unitOfWork;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
private readonly SessionCurrencyService _sessionCurrencyService;
|
||||
private readonly CurrencyConverterService _currencyConverterService;
|
||||
|
||||
public GetVehicleEnrollmentQueryHandler(
|
||||
UnitOfWork unitOfWork,
|
||||
IMapper mapper)
|
||||
IMapper mapper,
|
||||
SessionCurrencyService sessionCurrencyService,
|
||||
CurrencyConverterService currencyConverterService)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
_sessionCurrencyService = sessionCurrencyService;
|
||||
_currencyConverterService = currencyConverterService;
|
||||
}
|
||||
|
||||
public async Task<VehicleEnrollmentDto> Handle(
|
||||
@ -51,6 +60,23 @@ public class GetVehicleEnrollmentQueryHandler :
|
||||
.First(ra => ra.Id == rad.RouteAddressId);
|
||||
}
|
||||
|
||||
|
||||
// Convert currency
|
||||
|
||||
// TODO: Replace with AutoMapper Resolver
|
||||
|
||||
if (!_sessionCurrencyService.Currency.Equals(Currency.Default))
|
||||
{
|
||||
foreach (var rad in entity.RouteAddressDetails)
|
||||
{
|
||||
rad.CostToNextAddress = await _currencyConverterService
|
||||
.ConvertAsync(rad.CostToNextAddress, entity.Currency,
|
||||
_sessionCurrencyService.Currency, cancellationToken);
|
||||
}
|
||||
entity.Currency = _sessionCurrencyService.Currency;
|
||||
}
|
||||
|
||||
|
||||
_unitOfWork.Dispose();
|
||||
|
||||
return _mapper.Map<VehicleEnrollmentDto>(entity);
|
||||
|
@ -3,6 +3,8 @@ using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
|
||||
using AutoMapper;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using cuqmbr.TravelGuide.Application.Common.Extensions;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Queries.GetVehicleEnrollmentsPage;
|
||||
@ -13,12 +15,19 @@ public class GetVehicleEnrollmentsPageQueryHandler :
|
||||
private readonly UnitOfWork _unitOfWork;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
private readonly SessionCurrencyService _sessionCurrencyService;
|
||||
private readonly CurrencyConverterService _currencyConverterService;
|
||||
|
||||
public GetVehicleEnrollmentsPageQueryHandler(
|
||||
UnitOfWork unitOfWork,
|
||||
IMapper mapper)
|
||||
IMapper mapper,
|
||||
SessionCurrencyService sessionCurrencyService,
|
||||
CurrencyConverterService currencyConverterService)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
_sessionCurrencyService = sessionCurrencyService;
|
||||
_currencyConverterService = currencyConverterService;
|
||||
}
|
||||
|
||||
public async Task<PaginatedList<VehicleEnrollmentDto>> Handle(
|
||||
@ -55,13 +64,15 @@ public class GetVehicleEnrollmentsPageQueryHandler :
|
||||
(request.ArrivalTimeGreaterThanOrEqual != null
|
||||
?
|
||||
e.DepartureTime.AddSeconds(e.RouteAddressDetails
|
||||
.Sum(rad => rad.TimeToNextAddress.TotalSeconds + rad.CurrentAddressStopTime.TotalSeconds)) >=
|
||||
.Sum(rad => rad.TimeToNextAddress.TotalSeconds +
|
||||
rad.CurrentAddressStopTime.TotalSeconds)) >=
|
||||
request.ArrivalTimeGreaterThanOrEqual
|
||||
: true) &&
|
||||
(request.ArrivalTimeLessThanOrEqual != null
|
||||
?
|
||||
e.DepartureTime.AddSeconds(e.RouteAddressDetails
|
||||
.Sum(rad => rad.TimeToNextAddress.TotalSeconds + rad.CurrentAddressStopTime.TotalSeconds)) <=
|
||||
.Sum(rad => rad.TimeToNextAddress.TotalSeconds +
|
||||
rad.CurrentAddressStopTime.TotalSeconds)) <=
|
||||
request.ArrivalTimeLessThanOrEqual
|
||||
: true) &&
|
||||
(request.TravelTimeGreaterThanOrEqual != null
|
||||
@ -141,6 +152,25 @@ public class GetVehicleEnrollmentsPageQueryHandler :
|
||||
}
|
||||
|
||||
|
||||
// Convert currency
|
||||
|
||||
// TODO: Replace with AutoMapper Resolver
|
||||
|
||||
if (!_sessionCurrencyService.Currency.Equals(Currency.Default))
|
||||
{
|
||||
foreach (var ve in paginatedList.Items)
|
||||
{
|
||||
foreach (var rad in ve.RouteAddressDetails)
|
||||
{
|
||||
rad.CostToNextAddress = await _currencyConverterService
|
||||
.ConvertAsync(rad.CostToNextAddress, ve.Currency,
|
||||
_sessionCurrencyService.Currency, cancellationToken);
|
||||
}
|
||||
ve.Currency = _sessionCurrencyService.Currency;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var mappedItems = _mapper
|
||||
.ProjectTo<VehicleEnrollmentDto>(paginatedList.Items.AsQueryable());
|
||||
|
||||
|
@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
using cuqmbr.TravelGuide.Identity.Exceptions;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Configuration.Identity;
|
||||
|
||||
@ -38,6 +39,8 @@ public static class Configuration
|
||||
"ef_migrations_history",
|
||||
configuration.Datastore.PartitionName);
|
||||
});
|
||||
options.ConfigureWarnings(w => w.Ignore(
|
||||
RelationalEventId.PendingModelChangesWarning));
|
||||
});
|
||||
|
||||
services
|
||||
|
@ -1,19 +1,21 @@
|
||||
// using Microsoft.Extensions.Configuration;
|
||||
// using Microsoft.Extensions.DependencyInjection;
|
||||
//
|
||||
// namespace cuqmbr.TravelGuide.Configuration.Infrastructure;
|
||||
//
|
||||
// public static class Configuration
|
||||
// {
|
||||
// public static IServiceCollection ConfigureInfrastructure(
|
||||
// this IServiceCollection services,
|
||||
// IConfiguration configuration)
|
||||
// {
|
||||
// services
|
||||
// .AddIdentity(configuration)
|
||||
// .AddAuthenticationWithJwt(configuration)
|
||||
// .AddServices();
|
||||
//
|
||||
// return services;
|
||||
// }
|
||||
// }
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Configuration.Infrastructure;
|
||||
|
||||
public static class Configuration
|
||||
{
|
||||
public static IServiceCollection ConfigureInfrastructure(
|
||||
this IServiceCollection services)
|
||||
{
|
||||
services
|
||||
.AddHttpClient();
|
||||
|
||||
services
|
||||
.AddScoped<
|
||||
CurrencyConverterService,
|
||||
ExchangeApiCurrencyConverterService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using cuqmbr.TravelGuide.Persistence.Exceptions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using cuqmbr.TravelGuide.Persistence.PostgreSql;
|
||||
using cuqmbr.TravelGuide.Persistence.InMemory;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Configuration.Persistence;
|
||||
|
||||
@ -34,6 +35,8 @@ public static class Configuration
|
||||
"ef_migrations_history",
|
||||
configuration.PartitionName);
|
||||
});
|
||||
options.ConfigureWarnings(w => w.Ignore(
|
||||
RelationalEventId.PendingModelChangesWarning));
|
||||
});
|
||||
|
||||
services
|
||||
|
@ -495,13 +495,23 @@
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "ACtnvl3H3M/f8Z42980JxsNu7V9PPbzys4vBs83ZewnsgKd7JeYK18OMPo0g+MxAHrpgMrjmlinXDiaSRPcVnA=="
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "1bCSQrGv9+bpF5MGKF6THbnRFUZqQDrWPA39NDeVW9djeHBmow8kX4SX6/8KkeKI8gmUDG7jsG/bVuNAcY/ATQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "9.0.4",
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.1",
|
||||
"contentHash": "elH2vmwNmsXuKmUeMQ4YW9ldXiF+gSGDgg1vORksob5POnpaI6caj1Hu8zaYbEuibhqCoWg0YRWDazBY3zjBfg==",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "IAucBcHYtiCmMyFag+Vrp5m+cjGRlDttJk9Vx7Dqpq+Ama4BzVUOk0JARQakgFFr7ZTBSgLKlHmtY5MiItB7Cg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2",
|
||||
"Microsoft.Extensions.Options": "8.0.2"
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Options": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": {
|
||||
@ -539,6 +549,19 @@
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.2"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Http": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "ezelU6HJgmq4862YoWuEbHGSV+JnfnonTSbNSJVh6n6wDehyiJn4hBtcK7rGbf2KO3QeSvK5y8E7uzn1oaRH5w==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Diagnostics": "9.0.4",
|
||||
"Microsoft.Extensions.Logging": "9.0.4",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Options": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Identity.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
@ -696,6 +719,11 @@
|
||||
"System.Security.Principal.Windows": "4.5.0"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "13.0.3",
|
||||
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
||||
},
|
||||
"Npgsql": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.3",
|
||||
@ -832,7 +860,9 @@
|
||||
"infrastructure": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Application": "[1.0.0, )"
|
||||
"Application": "[1.0.0, )",
|
||||
"Microsoft.Extensions.Http": "[9.0.4, )",
|
||||
"Newtonsoft.Json": "[13.0.3, )"
|
||||
}
|
||||
},
|
||||
"persistence": {
|
||||
|
@ -3,16 +3,23 @@ namespace cuqmbr.TravelGuide.Domain.Enums;
|
||||
// Do not forget to update the schema of your database when changing
|
||||
// this class (if you use it with a database)
|
||||
|
||||
// ISO-4217 Country Codes dated 2025-03-31
|
||||
// ISO-4217 Currency Codes dated 2025-03-31
|
||||
|
||||
public abstract class Currency : Enumeration<Currency>
|
||||
{
|
||||
public static readonly Currency Default = new DefaultCurrency();
|
||||
public static readonly Currency USD = new USDCurrency();
|
||||
public static readonly Currency EUR = new EURCurrency();
|
||||
public static readonly Currency UAH = new UAHCurrency();
|
||||
|
||||
protected Currency(int value, string name) : base(value, name) { }
|
||||
|
||||
// When no currency is specified
|
||||
private sealed class DefaultCurrency : Currency
|
||||
{
|
||||
public DefaultCurrency() : base(Int32.MaxValue, "DEFAULT") { }
|
||||
}
|
||||
|
||||
private sealed class USDCurrency : Currency
|
||||
{
|
||||
public USDCurrency() : base(840, "USD") { }
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNetCore.Localizer.Json" Version="1.0.1" />
|
||||
<PackageReference Include="MicroElements.Swashbuckle.FluentValidation" Version="6.1.0" />
|
||||
<!-- <PackageReference Include="MicroElements.Swashbuckle.FluentValidation" Version="6.1.0" /> -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using cuqmbr.TravelGuide.Configuration.Persistence;
|
||||
using cuqmbr.TravelGuide.Configuration.Application;
|
||||
using cuqmbr.TravelGuide.Configuration.Infrastructure;
|
||||
using cuqmbr.TravelGuide.Configuration.Identity;
|
||||
using cuqmbr.TravelGuide.Configuration.Configuration;
|
||||
using cuqmbr.TravelGuide.Configuration.Logging;
|
||||
@ -9,7 +10,7 @@ using cuqmbr.TravelGuide.HttpApi.Middlewares;
|
||||
using cuqmbr.TravelGuide.HttpApi.Swashbuckle.OperationFilters;
|
||||
using System.Net;
|
||||
using Swashbuckle.AspNetCore.SwaggerUI;
|
||||
using MicroElements.Swashbuckle.FluentValidation.AspNetCore;
|
||||
// using MicroElements.Swashbuckle.FluentValidation.AspNetCore;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System.Reflection;
|
||||
|
||||
@ -25,12 +26,13 @@ services.ConfigureLogging();
|
||||
|
||||
services.ConfigurePersistence();
|
||||
services.ConfigureIdentity();
|
||||
// services.AddInfrastructure();
|
||||
services.ConfigureInfrastructure();
|
||||
services.ConfigureApplication();
|
||||
|
||||
services.AddScoped<SessionUserService, AspNetSessionUserService>();
|
||||
services.AddScoped<TimeZoneService, AspNetTimeZoneService>();
|
||||
services.AddScoped<CultureService, AspNetCultureService>();
|
||||
services.AddScoped<TimeZoneService, AspNetTimeZoneService>();
|
||||
services.AddScoped<SessionCurrencyService, AspNetSessionCurrencyService>();
|
||||
|
||||
services.AddControllers();
|
||||
|
||||
@ -82,8 +84,18 @@ services.AddSwaggerGen(options =>
|
||||
"from IANA tz database (https://www.iana.org/time-zones).",
|
||||
Type = SecuritySchemeType.ApiKey
|
||||
});
|
||||
|
||||
// Set Accept-Currency header in Authorize window
|
||||
options.OperationFilter<AcceptCurrencyHeaderOperationFilter>();
|
||||
options.AddSecurityDefinition("Accept-Currency", new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Name = "Accept-Currency",
|
||||
Description = "ISO-4217 Currency Code.",
|
||||
Type = SecuritySchemeType.ApiKey
|
||||
});
|
||||
});
|
||||
services.AddFluentValidationRulesToSwagger();
|
||||
// services.AddFluentValidationRulesToSwagger();
|
||||
|
||||
|
||||
services.AddScoped<ThreadCultureSetterMiddleware>();
|
||||
|
38
src/HttpApi/Services/AspNetSessionCurrencyService.cs
Normal file
38
src/HttpApi/Services/AspNetSessionCurrencyService.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
|
||||
namespace cuqmbr.TravelGuide.HttpApi.Services;
|
||||
|
||||
public sealed class AspNetSessionCurrencyService : SessionCurrencyService
|
||||
{
|
||||
private readonly HttpContext _httpContext;
|
||||
|
||||
public AspNetSessionCurrencyService(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_httpContext = httpContextAccessor.HttpContext;
|
||||
}
|
||||
|
||||
public Currency Currency
|
||||
{
|
||||
get
|
||||
{
|
||||
string? headerCurrencyCode =
|
||||
_httpContext.Request.Headers["Accept-Currency"];
|
||||
|
||||
string currencyCode =
|
||||
headerCurrencyCode?.ToUpper() ??
|
||||
cuqmbr.TravelGuide.Domain.Enums.Currency.Default.Name;
|
||||
|
||||
var resultCurrency =
|
||||
cuqmbr.TravelGuide.Domain.Enums.Currency.FromName(currencyCode);
|
||||
|
||||
if (resultCurrency == null)
|
||||
{
|
||||
resultCurrency =
|
||||
cuqmbr.TravelGuide.Domain.Enums.Currency.Default;
|
||||
}
|
||||
|
||||
return resultCurrency;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace cuqmbr.TravelGuide.HttpApi.Swashbuckle.OperationFilters;
|
||||
|
||||
public class AcceptCurrencyHeaderOperationFilter : IOperationFilter
|
||||
{
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
// TODO: Remove security requirements
|
||||
operation.Security ??= new List<OpenApiSecurityRequirement>();
|
||||
|
||||
var acceptCurrency = new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Accept-Currency"
|
||||
}
|
||||
};
|
||||
|
||||
operation.Security.Add(new OpenApiSecurityRequirement
|
||||
{
|
||||
[acceptCurrency] = new List<string>()
|
||||
});
|
||||
|
||||
if (operation.Parameters == null)
|
||||
operation.Parameters = new List<OpenApiParameter>();
|
||||
|
||||
operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Name = "Accept-Currency",
|
||||
Description = "ISO-4217 Currency Code.",
|
||||
In = ParameterLocation.Header,
|
||||
Schema = new OpenApiSchema { Type = "String" },
|
||||
Required = false
|
||||
});
|
||||
}
|
||||
}
|
@ -13,17 +13,6 @@
|
||||
"Microsoft.Extensions.Localization": "9.0.0"
|
||||
}
|
||||
},
|
||||
"MicroElements.Swashbuckle.FluentValidation": {
|
||||
"type": "Direct",
|
||||
"requested": "[6.1.0, )",
|
||||
"resolved": "6.1.0",
|
||||
"contentHash": "VzqApLPY8xIqXDvWqRuvoDYEoCHII42c4LgvLO3BikKoIVcECD+ZSG727I7yPZ/J07VEoa8aJddoqUtSm4E4gw==",
|
||||
"dependencies": {
|
||||
"FluentValidation": "[10.0.0, 12.0.0)",
|
||||
"MicroElements.OpenApi.FluentValidation": "6.1.0",
|
||||
"Swashbuckle.AspNetCore.SwaggerGen": "[6.3.0, 8.0.0)"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.OpenApi": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.4, )",
|
||||
@ -159,17 +148,6 @@
|
||||
"resolved": "2.0.1",
|
||||
"contentHash": "FYv95bNT4UwcNA+G/J1oX5OpRiSUxteXaUt2BJbRSdRNiIUNbggJF69wy6mnk2wYToaanpdXZdCwVylt96MpwQ=="
|
||||
},
|
||||
"MicroElements.OpenApi.FluentValidation": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.1.0",
|
||||
"contentHash": "qJPAI3bL70ND6fIi4bGqQf/lpV9wUod23R7JVhVVYRonoT/ZGmPBjMbO//IPPy3yP2F21iMX05brYjyU4/WqwQ==",
|
||||
"dependencies": {
|
||||
"FluentValidation": "[10.0.0, 12.0.0)",
|
||||
"Microsoft.AspNetCore.Http.Abstractions": "2.2.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "6.0.0",
|
||||
"Microsoft.Extensions.Options": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Authentication": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.3.0",
|
||||
@ -634,13 +612,23 @@
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "ACtnvl3H3M/f8Z42980JxsNu7V9PPbzys4vBs83ZewnsgKd7JeYK18OMPo0g+MxAHrpgMrjmlinXDiaSRPcVnA=="
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "1bCSQrGv9+bpF5MGKF6THbnRFUZqQDrWPA39NDeVW9djeHBmow8kX4SX6/8KkeKI8gmUDG7jsG/bVuNAcY/ATQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "9.0.4",
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.1",
|
||||
"contentHash": "elH2vmwNmsXuKmUeMQ4YW9ldXiF+gSGDgg1vORksob5POnpaI6caj1Hu8zaYbEuibhqCoWg0YRWDazBY3zjBfg==",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "IAucBcHYtiCmMyFag+Vrp5m+cjGRlDttJk9Vx7Dqpq+Ama4BzVUOk0JARQakgFFr7ZTBSgLKlHmtY5MiItB7Cg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2",
|
||||
"Microsoft.Extensions.Options": "8.0.2"
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Options": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": {
|
||||
@ -678,6 +666,19 @@
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.2"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Http": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "ezelU6HJgmq4862YoWuEbHGSV+JnfnonTSbNSJVh6n6wDehyiJn4hBtcK7rGbf2KO3QeSvK5y8E7uzn1oaRH5w==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Diagnostics": "9.0.4",
|
||||
"Microsoft.Extensions.Logging": "9.0.4",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Options": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Identity.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
@ -873,6 +874,11 @@
|
||||
"System.CodeDom": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "13.0.3",
|
||||
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
||||
},
|
||||
"Npgsql": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.3",
|
||||
@ -1119,7 +1125,9 @@
|
||||
"infrastructure": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Application": "[1.0.0, )"
|
||||
"Application": "[1.0.0, )",
|
||||
"Microsoft.Extensions.Http": "[9.0.4, )",
|
||||
"Newtonsoft.Json": "[13.0.3, )"
|
||||
}
|
||||
},
|
||||
"persistence": {
|
||||
|
@ -10,6 +10,11 @@
|
||||
<ProjectReference Include="..\Application\Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.4" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
|
@ -0,0 +1,101 @@
|
||||
using System.Globalization;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
// https://github.com/fawazahmed0/exchange-api
|
||||
|
||||
public sealed class ExchangeApiCurrencyConverterService :
|
||||
CurrencyConverterService
|
||||
{
|
||||
private readonly
|
||||
IDictionary<
|
||||
DateOnly, IDictionary<
|
||||
Currency, IDictionary<
|
||||
Currency, decimal>>> _cache;
|
||||
|
||||
private const string urlFormat = "https://cdn.jsdelivr.net/" +
|
||||
"npm/@fawazahmed0/currency-api@{0}/v1/currencies/{1}.json";
|
||||
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public ExchangeApiCurrencyConverterService(
|
||||
IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
|
||||
_cache =
|
||||
new Dictionary<
|
||||
DateOnly, IDictionary<
|
||||
Currency, IDictionary<
|
||||
Currency, decimal>>>();
|
||||
}
|
||||
|
||||
public async Task<decimal> ConvertAsync(decimal amount, Currency from,
|
||||
Currency to, CancellationToken cancellationToken)
|
||||
{
|
||||
return await ConvertAsync(amount, from, to,
|
||||
DateTimeOffset.UtcNow, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<decimal> ConvertAsync(decimal amount, Currency from,
|
||||
Currency to, DateTimeOffset time, CancellationToken cancellationToken)
|
||||
{
|
||||
if (from.Equals(to))
|
||||
{
|
||||
return amount;
|
||||
}
|
||||
|
||||
var requestDate = DateOnly.FromDateTime(time.ToUniversalTime().Date);
|
||||
|
||||
// Return cached value if available
|
||||
if (_cache.Keys.Contains(requestDate) &&
|
||||
_cache[requestDate].Keys.Contains(from) &&
|
||||
_cache[requestDate][from].Keys.Contains(to))
|
||||
{
|
||||
return amount * _cache[requestDate][from][to];
|
||||
}
|
||||
|
||||
|
||||
// Retreive new value from api
|
||||
|
||||
var httpClient = _httpClientFactory.CreateClient();
|
||||
|
||||
var requestDateString =
|
||||
requestDate == DateOnly.FromDateTime(DateTime.UtcNow) ?
|
||||
"latest" :
|
||||
requestDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
|
||||
|
||||
var url = String.Format(urlFormat,
|
||||
requestDateString, from.Name.ToLower());
|
||||
|
||||
var jsonString =
|
||||
await httpClient.GetStringAsync(url, cancellationToken);
|
||||
|
||||
var obj = JsonConvert.DeserializeObject<dynamic>(jsonString);
|
||||
|
||||
decimal rate = obj[from.Name.ToLower()][to.Name.ToLower()];
|
||||
|
||||
|
||||
// Cache new value
|
||||
|
||||
if (!_cache.Keys.Contains(requestDate))
|
||||
{
|
||||
_cache.Add(requestDate,
|
||||
new Dictionary<Currency, IDictionary<Currency, decimal>>());
|
||||
}
|
||||
|
||||
if (!_cache[requestDate].Keys.Contains(from))
|
||||
{
|
||||
_cache[requestDate].Add(from, new Dictionary<Currency, decimal>());
|
||||
}
|
||||
|
||||
if (!_cache[requestDate][from].Keys.Contains(to))
|
||||
{
|
||||
_cache[requestDate][from].Add(to, rate);
|
||||
}
|
||||
|
||||
|
||||
return amount * rate;
|
||||
}
|
||||
}
|
@ -2,6 +2,26 @@
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net9.0": {
|
||||
"Microsoft.Extensions.Http": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.4, )",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "ezelU6HJgmq4862YoWuEbHGSV+JnfnonTSbNSJVh6n6wDehyiJn4hBtcK7rGbf2KO3QeSvK5y8E7uzn1oaRH5w==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Diagnostics": "9.0.4",
|
||||
"Microsoft.Extensions.Logging": "9.0.4",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Options": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Direct",
|
||||
"requested": "[13.0.3, )",
|
||||
"resolved": "13.0.3",
|
||||
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
||||
},
|
||||
"AspNetCore.Localizer.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.1",
|
||||
@ -97,6 +117,31 @@
|
||||
"Microsoft.Extensions.Primitives": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "KIVBrMbItnCJDd1RF4KEaE8jZwDJcDUJW5zXpbwQ05HNYTK1GveHxHK0B3SjgDJuR48GRACXAO+BLhL8h34S7g==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Primitives": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "0LN/DiIKvBrkqp7gkF3qhGIeZk6/B63PthAHjQsxymJfIBcz0kbf4/p/t4lMgggVxZ+flRi5xvTwlpPOoZk8fg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Binder": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "cdrjcl9RIcwt3ECbnpP0Gt1+pkjdW90mq5yFYy8D9qRj2NqFFcv3yDp141iEamsd9E218sGxK8WHaIOcrqgDJg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
@ -110,6 +155,25 @@
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "UI0TQPVkS78bFdjkTodmkH0Fe8lXv9LnhGFKgKrsgUJ5a5FVdFRcgjIkBVLbGgdRhxWirxH/8IXUtEyYJx6GQg=="
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "1bCSQrGv9+bpF5MGKF6THbnRFUZqQDrWPA39NDeVW9djeHBmow8kX4SX6/8KkeKI8gmUDG7jsG/bVuNAcY/ATQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "9.0.4",
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "IAucBcHYtiCmMyFag+Vrp5m+cjGRlDttJk9Vx7Dqpq+Ama4BzVUOk0JARQakgFFr7ZTBSgLKlHmtY5MiItB7Cg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Options": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Localization": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
@ -153,6 +217,18 @@
|
||||
"Microsoft.Extensions.Primitives": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "aridVhAT3Ep+vsirR1pzjaOw0Jwiob6dc73VFQn2XmDfBA2X98M8YKO1GarvsXRX7gX1Aj+hj2ijMzrMHDOm0A==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Configuration.Binder": "9.0.4",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Options": "9.0.4",
|
||||
"Microsoft.Extensions.Primitives": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
|
@ -497,13 +497,23 @@
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "ACtnvl3H3M/f8Z42980JxsNu7V9PPbzys4vBs83ZewnsgKd7JeYK18OMPo0g+MxAHrpgMrjmlinXDiaSRPcVnA=="
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "1bCSQrGv9+bpF5MGKF6THbnRFUZqQDrWPA39NDeVW9djeHBmow8kX4SX6/8KkeKI8gmUDG7jsG/bVuNAcY/ATQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "9.0.4",
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.1",
|
||||
"contentHash": "elH2vmwNmsXuKmUeMQ4YW9ldXiF+gSGDgg1vORksob5POnpaI6caj1Hu8zaYbEuibhqCoWg0YRWDazBY3zjBfg==",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "IAucBcHYtiCmMyFag+Vrp5m+cjGRlDttJk9Vx7Dqpq+Ama4BzVUOk0JARQakgFFr7ZTBSgLKlHmtY5MiItB7Cg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2",
|
||||
"Microsoft.Extensions.Options": "8.0.2"
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Options": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": {
|
||||
@ -541,6 +551,19 @@
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.2"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Http": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "ezelU6HJgmq4862YoWuEbHGSV+JnfnonTSbNSJVh6n6wDehyiJn4hBtcK7rGbf2KO3QeSvK5y8E7uzn1oaRH5w==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Diagnostics": "9.0.4",
|
||||
"Microsoft.Extensions.Logging": "9.0.4",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
|
||||
"Microsoft.Extensions.Options": "9.0.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Identity.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.4",
|
||||
@ -772,8 +795,8 @@
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "13.0.1",
|
||||
"contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
|
||||
"resolved": "13.0.3",
|
||||
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
||||
},
|
||||
"Npgsql": {
|
||||
"type": "Transitive",
|
||||
@ -1005,7 +1028,9 @@
|
||||
"infrastructure": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Application": "[1.0.0, )"
|
||||
"Application": "[1.0.0, )",
|
||||
"Microsoft.Extensions.Http": "[9.0.4, )",
|
||||
"Newtonsoft.Json": "[13.0.3, )"
|
||||
}
|
||||
},
|
||||
"persistence": {
|
||||
|
Loading…
Reference in New Issue
Block a user