diff --git a/jobs/Backend/ExchangeAPIIntegrationTest/ExchangeRateAPIIntegrationTest.csproj b/jobs/Backend/ExchangeAPIIntegrationTest/ExchangeRateAPIIntegrationTest.csproj
new file mode 100644
index 0000000000..fbbc859bc1
--- /dev/null
+++ b/jobs/Backend/ExchangeAPIIntegrationTest/ExchangeRateAPIIntegrationTest.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net10.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jobs/Backend/ExchangeAPIIntegrationTest/ExchangeRateControllerTest.cs b/jobs/Backend/ExchangeAPIIntegrationTest/ExchangeRateControllerTest.cs
new file mode 100644
index 0000000000..f636185de8
--- /dev/null
+++ b/jobs/Backend/ExchangeAPIIntegrationTest/ExchangeRateControllerTest.cs
@@ -0,0 +1,25 @@
+using ExchangeRateAPI.Controllers;
+using ExchangeRateAPI.Models;
+using ExchangeRateUpdater.Business;
+
+namespace ExchangeRateAPIIntegrationTest
+{
+ public class ExchangeRateControllerTest
+ {
+ private readonly ExchangeRateController __ExchangeRateController;
+
+ public ExchangeRateControllerTest()
+ {
+ __ExchangeRateController = new ExchangeRateController(new ExchangeRateProvider());
+ }
+
+ [Fact]
+ public async Task ExchangeRateController_GetAsync_ShouldReturnResultsAsync()
+ {
+ List _Response = await __ExchangeRateController.GetAsync();
+
+ Assert.NotNull(_Response);
+ Assert.Equal(6, _Response.Count);
+ }
+ }
+}
diff --git a/jobs/Backend/ExchangeRateAPI/Adapters/ExchangeRateAdapter.cs b/jobs/Backend/ExchangeRateAPI/Adapters/ExchangeRateAdapter.cs
new file mode 100644
index 0000000000..401c91b084
--- /dev/null
+++ b/jobs/Backend/ExchangeRateAPI/Adapters/ExchangeRateAdapter.cs
@@ -0,0 +1,25 @@
+using ExchangeRateAPI.Models;
+using ExchangeRateUpdater.Business;
+
+namespace ExchangeRateAPI.Adapters
+{
+ public static class ExchangeRateAdapter
+ {
+ public static List ConvertToCurrencyList(List currencyCodes)
+ => currencyCodes.Select(currencyCode => new Currency(currencyCode)).ToList();
+
+ private static ExchangeRateResponse ToExchangeRateResponse(this ExchangeRate exchangeRate)
+ {
+ return new ExchangeRateResponse
+ {
+ SourceCurrency = exchangeRate.SourceCurrency.Code,
+ TargetCurrency = exchangeRate.TargetCurrency.Code,
+ Value = exchangeRate.Value
+ };
+ }
+
+ public static List ToExchangeRateResponses(this List exchangeRates)
+ => exchangeRates.Select(exchangeRate => exchangeRate.ToExchangeRateResponse()).ToList();
+
+ }
+}
diff --git a/jobs/Backend/ExchangeRateAPI/Controllers/ExchangeRateController.cs b/jobs/Backend/ExchangeRateAPI/Controllers/ExchangeRateController.cs
new file mode 100644
index 0000000000..1db35f562d
--- /dev/null
+++ b/jobs/Backend/ExchangeRateAPI/Controllers/ExchangeRateController.cs
@@ -0,0 +1,40 @@
+using ExchangeRateAPI.Adapters;
+using ExchangeRateAPI.Models;
+using ExchangeRateUpdater.Business;
+using Microsoft.AspNetCore.Mvc;
+
+namespace ExchangeRateAPI.Controllers
+{
+ [ApiController]
+ [Route("[controller]")]
+ public class ExchangeRateController : ControllerBase
+ {
+ private readonly IExchangeRateProvider __ExchangeRateProvider;
+
+ private readonly List __Currencies =
+ [
+ "AUD",
+ "USD",
+ "EUR",
+ "CZK",
+ "JPY",
+ "KES",
+ "RUB",
+ "THB",
+ "TRY",
+ "XYZ"
+ ];
+
+ public ExchangeRateController(IExchangeRateProvider exchangeRateProvider)
+ {
+ __ExchangeRateProvider = exchangeRateProvider;
+ }
+
+ [HttpGet(Name = "GetExchangeRates")]
+ public async Task> GetAsync()
+ {
+ List _Response = (await __ExchangeRateProvider.GetExchangeRatesAsync(ExchangeRateAdapter.ConvertToCurrencyList(__Currencies))).ToExchangeRateResponses();
+ return _Response;
+ }
+ }
+}
diff --git a/jobs/Backend/ExchangeRateAPI/ExchangeRateAPI.csproj b/jobs/Backend/ExchangeRateAPI/ExchangeRateAPI.csproj
new file mode 100644
index 0000000000..39205ab9b5
--- /dev/null
+++ b/jobs/Backend/ExchangeRateAPI/ExchangeRateAPI.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jobs/Backend/ExchangeRateAPI/ExchangeRateAPI.http b/jobs/Backend/ExchangeRateAPI/ExchangeRateAPI.http
new file mode 100644
index 0000000000..1fff11c1f1
--- /dev/null
+++ b/jobs/Backend/ExchangeRateAPI/ExchangeRateAPI.http
@@ -0,0 +1,6 @@
+@ExchangeRateAPI_HostAddress = http://localhost:5164
+
+GET {{ExchangeRateAPI_HostAddress}}/exchangerate/
+Accept: application/json
+
+###
diff --git a/jobs/Backend/ExchangeRateAPI/Models/ExchangeRateResponse.cs b/jobs/Backend/ExchangeRateAPI/Models/ExchangeRateResponse.cs
new file mode 100644
index 0000000000..bad3d5e98d
--- /dev/null
+++ b/jobs/Backend/ExchangeRateAPI/Models/ExchangeRateResponse.cs
@@ -0,0 +1,16 @@
+using System.Text.Json.Serialization;
+
+namespace ExchangeRateAPI.Models
+{
+ public class ExchangeRateResponse
+ {
+ [JsonPropertyName("sourceCurrency")]
+ public string SourceCurrency { get; set; }
+
+ [JsonPropertyName("targetCurrency")]
+ public string TargetCurrency { get; set; }
+
+ [JsonPropertyName("value")]
+ public decimal Value { get; set; }
+ }
+}
diff --git a/jobs/Backend/ExchangeRateAPI/Program.cs b/jobs/Backend/ExchangeRateAPI/Program.cs
new file mode 100644
index 0000000000..2ab0f5c7f2
--- /dev/null
+++ b/jobs/Backend/ExchangeRateAPI/Program.cs
@@ -0,0 +1,31 @@
+using ExchangeRateUpdater.Business;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+builder.Services.AddControllers();
+builder.Services.AddSingleton();
+
+// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
+builder.Services.AddOpenApi();
+
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.UseHttpsRedirection();
+
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();
diff --git a/jobs/Backend/ExchangeRateAPI/Properties/launchSettings.json b/jobs/Backend/ExchangeRateAPI/Properties/launchSettings.json
new file mode 100644
index 0000000000..368d001b56
--- /dev/null
+++ b/jobs/Backend/ExchangeRateAPI/Properties/launchSettings.json
@@ -0,0 +1,38 @@
+{
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "http://localhost:5164"
+ },
+ "https": {
+ "commandName": "Project",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:7099;http://localhost:5164"
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ },
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:62116/",
+ "sslPort": 44304
+ }
+ }
+}
\ No newline at end of file
diff --git a/jobs/Backend/ExchangeRateAPI/appsettings.Development.json b/jobs/Backend/ExchangeRateAPI/appsettings.Development.json
new file mode 100644
index 0000000000..0c208ae918
--- /dev/null
+++ b/jobs/Backend/ExchangeRateAPI/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/jobs/Backend/ExchangeRateAPI/appsettings.json b/jobs/Backend/ExchangeRateAPI/appsettings.json
new file mode 100644
index 0000000000..10f68b8c8b
--- /dev/null
+++ b/jobs/Backend/ExchangeRateAPI/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/jobs/Backend/ExchangeRateAPITest/ExchangeRateAPIUnitTest.csproj b/jobs/Backend/ExchangeRateAPITest/ExchangeRateAPIUnitTest.csproj
new file mode 100644
index 0000000000..e4437beb7d
--- /dev/null
+++ b/jobs/Backend/ExchangeRateAPITest/ExchangeRateAPIUnitTest.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net10.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jobs/Backend/ExchangeRateAPITest/ExchangeRateControllerTest.cs b/jobs/Backend/ExchangeRateAPITest/ExchangeRateControllerTest.cs
new file mode 100644
index 0000000000..04057ec421
--- /dev/null
+++ b/jobs/Backend/ExchangeRateAPITest/ExchangeRateControllerTest.cs
@@ -0,0 +1,38 @@
+using ExchangeRateAPI.Controllers;
+using ExchangeRateAPI.Models;
+using ExchangeRateUpdater.Business;
+using Moq;
+
+namespace ExchangeRateAPIUnitTest
+{
+ public class ExchangeRateControllerTest
+ {
+ private readonly ExchangeRateController __ExchangeRateController;
+ private readonly Mock __ExchangeRateProviderMock;
+
+ public ExchangeRateControllerTest()
+ {
+ __ExchangeRateProviderMock = new Mock();
+ __ExchangeRateController = new ExchangeRateController(__ExchangeRateProviderMock.Object);
+ }
+
+ [Fact]
+ public async Task ExchangeRateControllerTest_GetAsync_ShouldReturnResultsAsync()
+ {
+ List _ExchangeRateMock =
+ [
+ new ExchangeRate(new Currency("USD"), new Currency("CZK"), 20.43m),
+ new ExchangeRate(new Currency("AUD"), new Currency("CZK"), 14.48m),
+ ];
+
+ __ExchangeRateProviderMock
+ .Setup(mock => mock.GetExchangeRatesAsync(It.IsAny>()))
+ .Returns(Task.FromResult(_ExchangeRateMock));
+
+ List _Response = await __ExchangeRateController.GetAsync();
+
+ Assert.NotNull(_Response);
+ Assert.Equal(2, _Response.Count);
+ }
+ }
+}
diff --git a/jobs/Backend/ExchangeRateUpdaterUnitTest/ExchangeRateProviderTest.cs b/jobs/Backend/ExchangeRateUpdaterUnitTest/ExchangeRateProviderTest.cs
new file mode 100644
index 0000000000..dee43bc64c
--- /dev/null
+++ b/jobs/Backend/ExchangeRateUpdaterUnitTest/ExchangeRateProviderTest.cs
@@ -0,0 +1,46 @@
+using ExchangeRateUpdater.Business;
+using ExchangeRateUpdater.Business.Models;
+using ExchangeRateUpdater.Business.Services;
+using Moq;
+
+namespace ExchangeRateUpdaterUnitTest
+{
+ public class ExchangeRateProviderTest
+ {
+ private readonly IExchangeRateProvider __ExchangeRateProvider;
+ private readonly Mock __ExchangeRateServiceMock;
+
+ public ExchangeRateProviderTest()
+ {
+ __ExchangeRateServiceMock = new Mock();
+ __ExchangeRateProvider = new ExchangeRateProvider(__ExchangeRateServiceMock.Object);
+ }
+
+ [Fact]
+ public async Task ExchangeRateProvider_GetExchangeRatesAsync_ShouldReturnResultsAsync()
+ {
+ List _ExchangeRateModels =
+ [
+ new ExchangeRateModel() { CurrencyCode = "USD", Rate = 20.43m },
+ new ExchangeRateModel() { CurrencyCode = "AUD", Rate = 14.48m },
+ new ExchangeRateModel() { CurrencyCode = "BRL", Rate = 3.92m },
+ ];
+
+ __ExchangeRateServiceMock
+ .Setup(mock => mock.GetExchangeRatesAsync())
+ .Returns(Task.FromResult(_ExchangeRateModels))
+ .Verifiable();
+
+ List _Currencies =
+ [
+ new Currency("AUD"),
+ new Currency("USD")
+ ];
+
+ List _Results = await __ExchangeRateProvider.GetExchangeRatesAsync(_Currencies);
+
+ Assert.NotNull(_Results);
+ Assert.Equal(2, _Results.Count);
+ }
+ }
+}
diff --git a/jobs/Backend/ExchangeRateUpdaterUnitTest/ExchangeRateUpdaterUnitTest.csproj b/jobs/Backend/ExchangeRateUpdaterUnitTest/ExchangeRateUpdaterUnitTest.csproj
new file mode 100644
index 0000000000..7dde928f18
--- /dev/null
+++ b/jobs/Backend/ExchangeRateUpdaterUnitTest/ExchangeRateUpdaterUnitTest.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net10.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jobs/Backend/Task/Currency.cs b/jobs/Backend/Task/Business/Currency.cs
similarity index 89%
rename from jobs/Backend/Task/Currency.cs
rename to jobs/Backend/Task/Business/Currency.cs
index f375776f25..976d8ed377 100644
--- a/jobs/Backend/Task/Currency.cs
+++ b/jobs/Backend/Task/Business/Currency.cs
@@ -1,4 +1,4 @@
-namespace ExchangeRateUpdater
+namespace ExchangeRateUpdater.Business
{
public class Currency
{
diff --git a/jobs/Backend/Task/ExchangeRate.cs b/jobs/Backend/Task/Business/ExchangeRate.cs
similarity index 92%
rename from jobs/Backend/Task/ExchangeRate.cs
rename to jobs/Backend/Task/Business/ExchangeRate.cs
index 58c5bb10e0..3cae44f113 100644
--- a/jobs/Backend/Task/ExchangeRate.cs
+++ b/jobs/Backend/Task/Business/ExchangeRate.cs
@@ -1,4 +1,4 @@
-namespace ExchangeRateUpdater
+namespace ExchangeRateUpdater.Business
{
public class ExchangeRate
{
diff --git a/jobs/Backend/Task/Business/ExchangeRateProvider.cs b/jobs/Backend/Task/Business/ExchangeRateProvider.cs
new file mode 100644
index 0000000000..fdbaa9e632
--- /dev/null
+++ b/jobs/Backend/Task/Business/ExchangeRateProvider.cs
@@ -0,0 +1,53 @@
+using ExchangeRateUpdater.Business.Models;
+using ExchangeRateUpdater.Business.Services;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace ExchangeRateUpdater.Business
+{
+ public class ExchangeRateProvider : IExchangeRateProvider
+ {
+ private const string DEFAULT_CURRENCY = "CZK";
+
+ // This list represents the full list of exchange rates retrieved daily by calling the CNB API.
+ // Further improvement would be to keep track of the last time the API was called to see if it's time to update the database.
+ private static List __ExchangeRates = new();
+
+ private readonly IExchangeRateService __ExchangeRateService;
+ ///
+ /// Should return exchange rates among the specified currencies that are defined by the source. But only those defined
+ /// by the source, do not return calculated exchange rates. E.g. if the source contains "CZK/USD" but not "USD/CZK",
+ /// do not return exchange rate "USD/CZK" with value calculated as 1 / "CZK/USD". If the source does not provide
+ /// some of the currencies, ignore them.
+ ///
+ public ExchangeRateProvider()
+ : this(new ExchangeRateService())
+ { }
+
+ public ExchangeRateProvider(IExchangeRateService exchangeRateService)
+ {
+ __ExchangeRateService = exchangeRateService ?? throw new ArgumentNullException(nameof(exchangeRateService));
+ }
+
+ public async Task> GetExchangeRatesAsync(IEnumerable currencies)
+ {
+ if (__ExchangeRates.Count == 0)
+ {
+ List _Results = await __ExchangeRateService.GetExchangeRatesAsync();
+ if (_Results != null && _Results.Any())
+ {
+ __ExchangeRates = _Results.Select(result => new ExchangeRate(new Currency(DEFAULT_CURRENCY), new Currency(result.CurrencyCode), result.Rate)).ToList();
+ }
+ }
+
+ if (currencies != null && currencies.Any())
+ {
+ return __ExchangeRates.Where(result => currencies.Any(currency => currency.Code == result.TargetCurrency.Code)).ToList();
+ }
+
+ return __ExchangeRates;
+ }
+ }
+}
diff --git a/jobs/Backend/Task/Business/IExchangeRateProvider.cs b/jobs/Backend/Task/Business/IExchangeRateProvider.cs
new file mode 100644
index 0000000000..8abdede94c
--- /dev/null
+++ b/jobs/Backend/Task/Business/IExchangeRateProvider.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace ExchangeRateUpdater.Business
+{
+ public interface IExchangeRateProvider
+ {
+ Task> GetExchangeRatesAsync(IEnumerable currencies);
+ }
+}
\ No newline at end of file
diff --git a/jobs/Backend/Task/Business/Models/ExchangeRateModel.cs b/jobs/Backend/Task/Business/Models/ExchangeRateModel.cs
new file mode 100644
index 0000000000..c61f9c2c32
--- /dev/null
+++ b/jobs/Backend/Task/Business/Models/ExchangeRateModel.cs
@@ -0,0 +1,13 @@
+using System.Text.Json.Serialization;
+
+namespace ExchangeRateUpdater.Business.Models
+{
+ public class ExchangeRateModel
+ {
+ [JsonPropertyName("currencyCode")]
+ public string CurrencyCode { get; set; }
+
+ [JsonPropertyName("rate")]
+ public decimal Rate { get; set; }
+ }
+}
diff --git a/jobs/Backend/Task/Business/Models/ExchangeRateResponseModel.cs b/jobs/Backend/Task/Business/Models/ExchangeRateResponseModel.cs
new file mode 100644
index 0000000000..68ff3ebce0
--- /dev/null
+++ b/jobs/Backend/Task/Business/Models/ExchangeRateResponseModel.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace ExchangeRateUpdater.Business.Models
+{
+ public class ExchangeRateResponseModel
+ {
+ [JsonPropertyName("rates")]
+ public List ExchangeRates{ get; set; }
+ }
+}
diff --git a/jobs/Backend/Task/Business/Parameters/ExchangeRateParameters.cs b/jobs/Backend/Task/Business/Parameters/ExchangeRateParameters.cs
new file mode 100644
index 0000000000..8370796ed7
--- /dev/null
+++ b/jobs/Backend/Task/Business/Parameters/ExchangeRateParameters.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace ExchangeRateUpdater.Business.Parameters
+{
+ /**
+ * This class represents a set of possible parameterised details regarding the exchange rate for the Czech National Bank.
+ * These could be:
+ * - viewed/updated from a frontend parameter page
+ * - retrieved from a database with an entity/repository pattern before using them in the service class that performs the API call.
+ */
+ internal class ExchangeRateParameters : IExchangeRateParameters
+ {
+ public string BaseURL { get; set; } = "https://api.cnb.cz/cnbapi/";
+ public string ExchangeRatesDailyEndpoint { get; set; } = "exrates/daily";
+ public DateTime Date { get; set; } = DateTime.Now;
+ public string Language { get; set; } = "EN";
+ }
+}
diff --git a/jobs/Backend/Task/Business/Parameters/IExchangeRateParameters.cs b/jobs/Backend/Task/Business/Parameters/IExchangeRateParameters.cs
new file mode 100644
index 0000000000..46e408d0db
--- /dev/null
+++ b/jobs/Backend/Task/Business/Parameters/IExchangeRateParameters.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace ExchangeRateUpdater.Business.Parameters
+{
+ internal interface IExchangeRateParameters
+ {
+ string BaseURL { get; set; }
+ string ExchangeRatesDailyEndpoint { get; set; }
+ DateTime Date { get; set; }
+ string Language { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/jobs/Backend/Task/Business/Services/ExchangeRateService.cs b/jobs/Backend/Task/Business/Services/ExchangeRateService.cs
new file mode 100644
index 0000000000..c7be5d3621
--- /dev/null
+++ b/jobs/Backend/Task/Business/Services/ExchangeRateService.cs
@@ -0,0 +1,48 @@
+using ExchangeRateUpdater.Business.Models;
+using ExchangeRateUpdater.Business.Parameters;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Net.Http.Json;
+using System.Threading.Tasks;
+
+namespace ExchangeRateUpdater.Business.Services
+{
+ public class ExchangeRateService : IExchangeRateService
+ {
+ private readonly HttpClient __Client;
+ private readonly IExchangeRateParameters __ExchangeRateParameters;
+ private readonly ILogger __Logger;
+
+ // Using dependency injection to better manage all dependencies and testing
+ public ExchangeRateService()
+ : this(new ExchangeRateParameters(), new HttpClient(), new LoggerFactory().CreateLogger())
+ { }
+
+ internal ExchangeRateService(IExchangeRateParameters exchangeRateParameters, HttpClient httpClient, ILogger logger)
+ {
+ // Business logic classes can all extend a base class that logs different types of exception, for example this ArgumentNullException
+ __ExchangeRateParameters = exchangeRateParameters ?? throw new ArgumentNullException(nameof(exchangeRateParameters));
+ __Client = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
+ __Logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ async Task> IExchangeRateService.GetExchangeRatesAsync()
+ {
+ List _ExchangeRates = new();
+ try
+ {
+ string _DailyRatesEndpoint = $"{__ExchangeRateParameters.BaseURL}{__ExchangeRateParameters.ExchangeRatesDailyEndpoint}?date={__ExchangeRateParameters.Date:yyyy-MM-dd}&lang={__ExchangeRateParameters.Language}";
+ ExchangeRateResponseModel _Response = await __Client.GetFromJsonAsync(new Uri(_DailyRatesEndpoint));
+ _ExchangeRates = _Response.ExchangeRates;
+ }
+ catch (Exception _Exception)
+ {
+ __Logger.LogError(_Exception, _Exception.Message);
+ }
+
+ return _ExchangeRates;
+ }
+ }
+}
diff --git a/jobs/Backend/Task/Business/Services/IExchangeRateService.cs b/jobs/Backend/Task/Business/Services/IExchangeRateService.cs
new file mode 100644
index 0000000000..f7ed4f09c1
--- /dev/null
+++ b/jobs/Backend/Task/Business/Services/IExchangeRateService.cs
@@ -0,0 +1,11 @@
+using ExchangeRateUpdater.Business.Models;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace ExchangeRateUpdater.Business.Services
+{
+ public interface IExchangeRateService
+ {
+ Task> GetExchangeRatesAsync();
+ }
+}
\ No newline at end of file
diff --git a/jobs/Backend/Task/ExchangeRateProvider.cs b/jobs/Backend/Task/ExchangeRateProvider.cs
deleted file mode 100644
index 6f82a97fbe..0000000000
--- a/jobs/Backend/Task/ExchangeRateProvider.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-
-namespace ExchangeRateUpdater
-{
- public class ExchangeRateProvider
- {
- ///
- /// Should return exchange rates among the specified currencies that are defined by the source. But only those defined
- /// by the source, do not return calculated exchange rates. E.g. if the source contains "CZK/USD" but not "USD/CZK",
- /// do not return exchange rate "USD/CZK" with value calculated as 1 / "CZK/USD". If the source does not provide
- /// some of the currencies, ignore them.
- ///
- public IEnumerable GetExchangeRates(IEnumerable currencies)
- {
- return Enumerable.Empty();
- }
- }
-}
diff --git a/jobs/Backend/Task/ExchangeRateUpdater.csproj b/jobs/Backend/Task/ExchangeRateUpdater.csproj
index 2fc654a12b..25595763f2 100644
--- a/jobs/Backend/Task/ExchangeRateUpdater.csproj
+++ b/jobs/Backend/Task/ExchangeRateUpdater.csproj
@@ -5,4 +5,9 @@
net6.0
+
+
+
+
+
\ No newline at end of file
diff --git a/jobs/Backend/Task/ExchangeRateUpdater.sln b/jobs/Backend/Task/ExchangeRateUpdater.sln
index 89be84daff..0916fd4948 100644
--- a/jobs/Backend/Task/ExchangeRateUpdater.sln
+++ b/jobs/Backend/Task/ExchangeRateUpdater.sln
@@ -1,10 +1,20 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 14
-VisualStudioVersion = 14.0.25123.0
+# Visual Studio Version 18
+VisualStudioVersion = 18.2.11415.280
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExchangeRateUpdater", "ExchangeRateUpdater.csproj", "{7B2695D6-D24C-4460-A58E-A10F08550CE0}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExchangeRateAPI", "..\ExchangeRateAPI\ExchangeRateAPI.csproj", "{75788C34-62FB-410B-8E99-99B487A742F9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExchangeRateAPIUnitTest", "..\ExchangeRateAPITest\ExchangeRateAPIUnitTest.csproj", "{525549F1-F0EB-4524-A9D9-0A2DCB121C69}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExchangeRateAPIIntegrationTest", "..\ExchangeAPIIntegrationTest\ExchangeRateAPIIntegrationTest.csproj", "{F6E46D30-FEE4-4579-8640-F3E7C155323F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExchangeRateUpdaterUnitTest", "..\ExchangeRateUpdaterUnitTest\ExchangeRateUpdaterUnitTest.csproj", "{A5D8D2C9-46EE-428C-94CF-AEAE0E522F3F}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3848E27F-625E-4712-93A9-4700B3AFF6C3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,8 +25,27 @@ Global
{7B2695D6-D24C-4460-A58E-A10F08550CE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7B2695D6-D24C-4460-A58E-A10F08550CE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7B2695D6-D24C-4460-A58E-A10F08550CE0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {75788C34-62FB-410B-8E99-99B487A742F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {75788C34-62FB-410B-8E99-99B487A742F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {75788C34-62FB-410B-8E99-99B487A742F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {75788C34-62FB-410B-8E99-99B487A742F9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {525549F1-F0EB-4524-A9D9-0A2DCB121C69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {525549F1-F0EB-4524-A9D9-0A2DCB121C69}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {525549F1-F0EB-4524-A9D9-0A2DCB121C69}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {525549F1-F0EB-4524-A9D9-0A2DCB121C69}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F6E46D30-FEE4-4579-8640-F3E7C155323F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F6E46D30-FEE4-4579-8640-F3E7C155323F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F6E46D30-FEE4-4579-8640-F3E7C155323F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F6E46D30-FEE4-4579-8640-F3E7C155323F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A5D8D2C9-46EE-428C-94CF-AEAE0E522F3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A5D8D2C9-46EE-428C-94CF-AEAE0E522F3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A5D8D2C9-46EE-428C-94CF-AEAE0E522F3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A5D8D2C9-46EE-428C-94CF-AEAE0E522F3F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {5F12761D-E3ED-4941-AA34-5A3AA4BA3FC5}
+ EndGlobalSection
EndGlobal
diff --git a/jobs/Backend/Task/Program.cs b/jobs/Backend/Task/Program.cs
index 379a69b1f8..c3e23cb089 100644
--- a/jobs/Backend/Task/Program.cs
+++ b/jobs/Backend/Task/Program.cs
@@ -1,6 +1,8 @@
-using System;
+using ExchangeRateUpdater.Business;
+using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
namespace ExchangeRateUpdater
{
@@ -8,6 +10,7 @@ public static class Program
{
private static IEnumerable currencies = new[]
{
+ new Currency("AUD"),
new Currency("USD"),
new Currency("EUR"),
new Currency("CZK"),
@@ -19,12 +22,12 @@ public static class Program
new Currency("XYZ")
};
- public static void Main(string[] args)
+ public static async Task Main(string[] args)
{
try
{
var provider = new ExchangeRateProvider();
- var rates = provider.GetExchangeRates(currencies);
+ var rates = await provider.GetExchangeRatesAsync(currencies);
Console.WriteLine($"Successfully retrieved {rates.Count()} exchange rates:");
foreach (var rate in rates)