diff --git a/.gitignore b/.gitignore
index fd3586545..e3f1a1f9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,23 +1,44 @@
-!.gitkeep
-!.gitignore
-!*.dll
-[Oo]bj
-[Bb]in
-*.user
+*.swp
+*.*~
+project.lock.json
+.DS_Store
+*.pyc
+nupkg/
+
+# Visual Studio Code
+.vscode/
+
+# Rider
+.idea/
+
+# Visual Studio
+.vs/
+
+# Fleet
+.fleet/
+
+# Code Rush
+.cr/
+
+# User-specific files
*.suo
-*.[Cc]ache
-*.bak
-*.ncb
-*.DS_Store
-*.userprefs
-*.iml
-*.ncrunch*
-.*crunch*.local.xml
-.idea
-[Tt]humbs.db
-*.tgz
-*.sublime-*
+*.user
+*.userosscache
+*.sln.docstates
-node_modules
-bower_components
-npm-debug.log
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+[Oo]ut/
+msbuild.log
+msbuild.err
+msbuild.wrn
+appsettings.Development.json
\ 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 6f82a97fb..000000000
--- 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
deleted file mode 100644
index 2fc654a12..000000000
--- a/jobs/Backend/Task/ExchangeRateUpdater.csproj
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- Exe
- net6.0
-
-
-
\ No newline at end of file
diff --git a/jobs/Backend/Task/ExchangeRateUpdater.sln b/jobs/Backend/Task/ExchangeRateUpdater.sln
deleted file mode 100644
index 89be84daf..000000000
--- a/jobs/Backend/Task/ExchangeRateUpdater.sln
+++ /dev/null
@@ -1,22 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 14
-VisualStudioVersion = 14.0.25123.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExchangeRateUpdater", "ExchangeRateUpdater.csproj", "{7B2695D6-D24C-4460-A58E-A10F08550CE0}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {7B2695D6-D24C-4460-A58E-A10F08550CE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {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
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
-EndGlobal
diff --git a/jobs/Backend/Task/Program.cs b/jobs/Backend/Task/Program.cs
deleted file mode 100644
index 379a69b1f..000000000
--- a/jobs/Backend/Task/Program.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace ExchangeRateUpdater
-{
- public static class Program
- {
- private static IEnumerable currencies = new[]
- {
- new Currency("USD"),
- new Currency("EUR"),
- new Currency("CZK"),
- new Currency("JPY"),
- new Currency("KES"),
- new Currency("RUB"),
- new Currency("THB"),
- new Currency("TRY"),
- new Currency("XYZ")
- };
-
- public static void Main(string[] args)
- {
- try
- {
- var provider = new ExchangeRateProvider();
- var rates = provider.GetExchangeRates(currencies);
-
- Console.WriteLine($"Successfully retrieved {rates.Count()} exchange rates:");
- foreach (var rate in rates)
- {
- Console.WriteLine(rate.ToString());
- }
- }
- catch (Exception e)
- {
- Console.WriteLine($"Could not retrieve exchange rates: '{e.Message}'.");
- }
-
- Console.ReadLine();
- }
- }
-}
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.App/.gitignore b/jobs/Backend/Task/src/ExchangeRateUpdater.App/.gitignore
new file mode 100644
index 000000000..005d62573
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.App/.gitignore
@@ -0,0 +1,44 @@
+*.swp
+*.*~
+project.lock.json
+.DS_Store
+*.pyc
+nupkg/
+
+# Visual Studio Code
+.vscode/
+
+# Rider
+.idea/
+
+# Visual Studio
+.vs/
+
+# Fleet
+.fleet/
+
+# Code Rush
+.cr/
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+*.sln
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+[Oo]ut/
+msbuild.log
+msbuild.err
+msbuild.wrn
+appsettings.Development.json
\ No newline at end of file
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.App/ExchangeRateUpdater.App.csproj b/jobs/Backend/Task/src/ExchangeRateUpdater.App/ExchangeRateUpdater.App.csproj
new file mode 100644
index 000000000..73906c041
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.App/ExchangeRateUpdater.App.csproj
@@ -0,0 +1,32 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.App/Extensions/ConverterExtension.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.App/Extensions/ConverterExtension.cs
new file mode 100644
index 000000000..ccf9ddc3f
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.App/Extensions/ConverterExtension.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ExchangeRateUpdater.Domain.DTO;
+using ExchangeRateUpdater.Domain.Models;
+
+namespace ExchangeRateUpdater.Extensions
+{
+ public static class ConverterExtension
+ {
+ public static IEnumerable ToExchangeRates(this ExchangeRatesDTO exchangeRates)
+ {
+ return exchangeRates.Rates.Select(r => new ExchangeRate(new Currency(r.CurrencyCode), new Currency("CZK"), r.Rate / r.Amount));
+ }
+ }
+}
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.App/Program.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.App/Program.cs
new file mode 100644
index 000000000..e93b228bb
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.App/Program.cs
@@ -0,0 +1,60 @@
+using ExchangeRateUpdater;
+using ExchangeRateUpdater.Domain.Configurations;
+using ExchangeRateUpdater.Providers;
+using ExchangeRateUpdater.Services;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Serilog;
+
+
+
+var config = new ConfigurationBuilder()
+ .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
+ .Build();
+Log.Logger = new LoggerConfiguration()
+ .ReadFrom.Configuration(config)
+ .CreateLogger();
+var host = new HostBuilder()
+ .ConfigureServices((services) =>
+ {
+
+ var settings = new ExchangeRateProviderSettings();
+ config.GetSection("ExchangeRateProviderSettings").Bind(settings);
+ services.AddHttpClient("exchange",
+ (serviceProvider, client) =>
+ {
+ client.BaseAddress = new Uri($"{settings.UrlBaseAPI}/{settings.UrlExchangeRate}");
+ })
+ .ConfigurePrimaryHttpMessageHandler(() =>
+ {
+ return new SocketsHttpHandler()
+ {
+ PooledConnectionLifetime = TimeSpan.FromMinutes(15)
+ };
+ })
+ .SetHandlerLifetime(Timeout.InfiniteTimeSpan);
+
+ services.AddTransient();
+ services.AddTransient();
+ services.AddSingleton();
+ })
+ .UseSerilog()
+ .Build();
+
+var exchangeRateProvider = host.Services.GetRequiredService();
+
+for (int i = 0; i < 3; i++)
+{
+ var rates = await exchangeRateProvider.GetExchangeRatesAsync(TestingData.currencies);
+ foreach (var rate in rates)
+ {
+ Console.WriteLine(rate.ToString());
+ }
+ Console.WriteLine("-----------------");
+
+}
+
+
+
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.App/Providers/ExchangeRateProvider.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.App/Providers/ExchangeRateProvider.cs
new file mode 100644
index 000000000..5b78dbd2f
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.App/Providers/ExchangeRateProvider.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.Configuration;
+using System.Net.Http;
+using ExchangeRateUpdater.Domain.Models;
+using Microsoft.Extensions.Logging;
+using ExchangeRateUpdater.Services;
+using System.Threading.Tasks;
+using ExchangeRateUpdater.Domain.DTO;
+namespace ExchangeRateUpdater.Providers
+{
+ public class ExchangeRateProvider(ILogger _logger, IExchangeRateService exchangeRateService) : IExchangeRateProvider
+ {
+ ///
+ /// 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 async Task> GetExchangeRatesAsync(IEnumerable currencies)
+ {
+ var result = new List();
+ try
+ {
+ _logger.LogInformation("Started fetching exchange rates");
+ var exchangeRates = await exchangeRateService.GetExchangeRateAsync();
+ foreach (var currency in currencies)
+ {
+ _logger.LogInformation($"Adding exchange rate for {currency.Code}");
+ result.AddRange(exchangeRates.Where(e => e.TargetCurrency.Code == currency.Code));
+ _logger.LogInformation($"Added exchange rate for {currency.Code}");
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, ex.Message);
+ return null;
+ }
+ return result;
+ }
+ }
+}
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.App/Providers/IExchangeRateProvider.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.App/Providers/IExchangeRateProvider.cs
new file mode 100644
index 000000000..7807667e0
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.App/Providers/IExchangeRateProvider.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ExchangeRateUpdater.Domain.DTO;
+using ExchangeRateUpdater.Domain.Models;
+using ExchangeRateUpdater.Services;
+
+namespace ExchangeRateUpdater.Providers
+{
+ public interface IExchangeRateProvider
+ {
+ public Task> GetExchangeRatesAsync(IEnumerable currencies);
+ }
+}
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.App/Services/ExchangeRateService.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.App/Services/ExchangeRateService.cs
new file mode 100644
index 000000000..d64bdd414
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.App/Services/ExchangeRateService.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Json;
+using System.Text;
+using System.Threading.Tasks;
+using ExchangeRateUpdater.Domain.Const;
+using ExchangeRateUpdater.Domain.DTO;
+using ExchangeRateUpdater.Domain.Models;
+using ExchangeRateUpdater.Extensions;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Logging;
+
+namespace ExchangeRateUpdater.Services
+{
+ public class ExchangeRateService(IHttpClientFactory clientFactory, ILogger _logger, IMemoryCache _memoryCache): IExchangeRateService
+ {
+ public async Task> GetExchangeRateAsync()
+ {
+ var cacheConstant = MemoryCacheConstants.ExchangeRateKey(nameof(ExchangeRate) ,DateTime.Now.Date);
+ if (_memoryCache.TryGetValue(cacheConstant, out IEnumerable result))
+ {
+ _logger.LogInformation("Exchange rates retrieved from cache");
+ return result;
+ }
+ using var client = clientFactory.CreateClient("exchange");
+ _logger.LogInformation($"Sending GET request for getting exchange rates for {DateTime.Now:yyyy-MM-dd}");
+ var response = await client.GetAsync(client.BaseAddress + $"?date={DateTime.Now:yyyy-MM-dd}");
+ if (!response.IsSuccessStatusCode)
+ {
+ _logger.LogError($"Status code: {response.StatusCode}; Failed to fetch rates on {DateTime.Now:yyyy-MM-dd}");
+ throw new Exception($"Status code: {response.StatusCode}; Failed to fetch rates on{DateTime.Now:yyyy-MM-dd}");
+ }
+ result = (await response.Content.ReadFromJsonAsync()).ToExchangeRates();
+ if (result.Any())
+ {
+ _memoryCache.Set(cacheConstant, result, TimeSpan.FromSeconds(60));
+ }
+ return result;
+ }
+ }
+}
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.App/Services/IExchangeRateService.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.App/Services/IExchangeRateService.cs
new file mode 100644
index 000000000..4bfe297e1
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.App/Services/IExchangeRateService.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ExchangeRateUpdater.Domain.DTO;
+using ExchangeRateUpdater.Domain.Models;
+
+namespace ExchangeRateUpdater.Services
+{
+ public interface IExchangeRateService
+ {
+ public Task> GetExchangeRateAsync();
+ }
+}
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.App/TestingData.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.App/TestingData.cs
new file mode 100644
index 000000000..66983c952
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.App/TestingData.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ExchangeRateUpdater.Domain.Models;
+
+namespace ExchangeRateUpdater
+{
+ public class TestingData
+ {
+ public static IEnumerable currencies = new[]
+ {
+ new Currency("USD"),
+ new Currency("EUR"),
+ new Currency("CZK"),
+ new Currency("JPY"),
+ new Currency("KES"),
+ new Currency("RUB"),
+ new Currency("THB"),
+ new Currency("TRY"),
+ new Currency("XYZ")
+ };
+ }
+}
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.App/appsettings.json b/jobs/Backend/Task/src/ExchangeRateUpdater.App/appsettings.json
new file mode 100644
index 000000000..4db5ccc0e
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.App/appsettings.json
@@ -0,0 +1,32 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*",
+
+ "ExchangeRateProviderSettings": {
+ "UrlBaseAPI": "https://api.cnb.cz",
+ "UrlExchangeRate": "cnbapi/exrates/daily"
+ },
+ "Serilog": {
+ "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
+ "MinimumLevel": {
+ "Default": "Information"
+ },
+ "WriteTo": [
+ {
+ "Name": "Console"
+ },
+ {
+ "Name": "File",
+ "Args": {
+ "path": "C:\\Temp\\logs\\log-.txt",
+ "rollingInterval": "Day"
+ }
+ }
+ ]
+ }
+ }
\ No newline at end of file
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/Configurations/ExchangeRateProviderSettings.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/Configurations/ExchangeRateProviderSettings.cs
new file mode 100644
index 000000000..546180279
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/Configurations/ExchangeRateProviderSettings.cs
@@ -0,0 +1,8 @@
+namespace ExchangeRateUpdater.Domain.Configurations
+{
+ public class ExchangeRateProviderSettings
+ {
+ public string? UrlBaseAPI { get; set; }
+ public string? UrlExchangeRate { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/Const/MemoryCacheConstants.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/Const/MemoryCacheConstants.cs
new file mode 100644
index 000000000..dbaffe3ac
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/Const/MemoryCacheConstants.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ExchangeRateUpdater.Domain.Const
+{
+
+ public class MemoryCacheConstants
+ {
+ public static string ExchangeRateKey(string className, DateTime? date)
+ {
+ return $"{className}_DATE_{date.GetValueOrDefault().ToString("yyyy-MM-dd")}_KEY";
+ }
+ }
+}
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/DTO/ExchangeRateDTO.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/DTO/ExchangeRateDTO.cs
new file mode 100644
index 000000000..53108a0e7
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/DTO/ExchangeRateDTO.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace ExchangeRateUpdater.Domain.DTO
+{
+ public class ExchangeRateDTO
+ {
+ [JsonPropertyName("amount")]
+ public int Amount { get; init; }
+
+ [JsonPropertyName("currencyCode")]
+ public string CurrencyCode { get; init; }
+
+ [JsonPropertyName("currency")]
+ public string Currency { get; init; }
+
+ [JsonPropertyName("country")]
+ public string Country { get; init; }
+
+ [JsonPropertyName("rate")]
+ public decimal Rate { get; init; }
+
+ }
+}
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/DTO/ExchangeRatesDTO.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/DTO/ExchangeRatesDTO.cs
new file mode 100644
index 000000000..dc23019a4
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/DTO/ExchangeRatesDTO.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace ExchangeRateUpdater.Domain.DTO
+{
+ public class ExchangeRatesDTO
+ {
+ [JsonPropertyName("rates")]
+ public IEnumerable Rates { get; set; } = new List();
+ }
+}
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/ExchangeRateUpdater.Domain.csproj b/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/ExchangeRateUpdater.Domain.csproj
new file mode 100644
index 000000000..0a8343153
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/ExchangeRateUpdater.Domain.csproj
@@ -0,0 +1,11 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
diff --git a/jobs/Backend/Task/Currency.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/Models/Currency.cs
similarity index 88%
rename from jobs/Backend/Task/Currency.cs
rename to jobs/Backend/Task/src/ExchangeRateUpdater.Domain/Models/Currency.cs
index f375776f2..a46a1e902 100644
--- a/jobs/Backend/Task/Currency.cs
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/Models/Currency.cs
@@ -1,4 +1,4 @@
-namespace ExchangeRateUpdater
+namespace ExchangeRateUpdater.Domain.Models
{
public class Currency
{
diff --git a/jobs/Backend/Task/ExchangeRate.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/Models/ExchangeRate.cs
similarity index 91%
rename from jobs/Backend/Task/ExchangeRate.cs
rename to jobs/Backend/Task/src/ExchangeRateUpdater.Domain/Models/ExchangeRate.cs
index 58c5bb10e..969a319c5 100644
--- a/jobs/Backend/Task/ExchangeRate.cs
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.Domain/Models/ExchangeRate.cs
@@ -1,5 +1,6 @@
-namespace ExchangeRateUpdater
+namespace ExchangeRateUpdater.Domain.Models
{
+
public class ExchangeRate
{
public ExchangeRate(Currency sourceCurrency, Currency targetCurrency, decimal value)
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.Tests/ExchangeRateProviderTests.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.Tests/ExchangeRateProviderTests.cs
new file mode 100644
index 000000000..161d71ead
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.Tests/ExchangeRateProviderTests.cs
@@ -0,0 +1,45 @@
+using ExchangeRateUpdater.Domain.Models;
+using ExchangeRateUpdater.Providers;
+using ExchangeRateUpdater.Services;
+using Microsoft.Extensions.Logging;
+using Moq;
+
+
+namespace ExchangeRateUpdater.Tests
+{
+ public class ExchangeRateProviderTests
+ {
+
+ private Mock> _loggerMock;
+ public readonly Mock exchangeRateServiceMock;
+ public ExchangeRateProvider exchangeRateProvider;
+
+ public IEnumerable testExchangeRatesData { get; set; }
+ public IEnumerable testCurrenciesData { get; set; }
+
+ public ExchangeRateProviderTests()
+ {
+ testExchangeRatesData = new List()
+ {
+ new ExchangeRate(new Currency("CZK"), new Currency("USD"), 0.89m),
+ new ExchangeRate(new Currency("CZK"), new Currency("EUR"), 0.86m),
+ new ExchangeRate(new Currency("CZK"), new Currency("AUD"), 1.37m),
+ new ExchangeRate(new Currency("CZK"), new Currency("GBP"), 0.0075m),
+ };
+ testCurrenciesData = new List(TestingData.currencies);
+
+ _loggerMock = new Mock>();
+ exchangeRateServiceMock = new Mock();
+ exchangeRateServiceMock.Setup(s => s.GetExchangeRateAsync()).ReturnsAsync(testExchangeRatesData);
+ }
+
+ [Fact]
+ public async Task GetExchangeRatesProvider_NotNullAsync()
+ {
+ exchangeRateProvider = new ExchangeRateProvider(_loggerMock.Object, exchangeRateServiceMock.Object);
+ var result = await exchangeRateProvider.GetExchangeRatesAsync(testCurrenciesData);
+ Assert.NotNull(result);
+ Assert.True(result.Count() > 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.Tests/ExchangeRateServiceTests.cs b/jobs/Backend/Task/src/ExchangeRateUpdater.Tests/ExchangeRateServiceTests.cs
new file mode 100644
index 000000000..03eb2154a
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.Tests/ExchangeRateServiceTests.cs
@@ -0,0 +1,65 @@
+using Castle.Core.Logging;
+using ExchangeRateUpdater.Domain.DTO;
+using ExchangeRateUpdater.Domain.Models;
+using ExchangeRateUpdater.Extensions;
+using ExchangeRateUpdater.Providers;
+using ExchangeRateUpdater.Services;
+using Microsoft.Extensions.Logging;
+using Moq;
+
+
+namespace ExchangeRateUpdater.Tests
+{
+ public class ExchangeRateServiceTests
+ {
+
+ public readonly Mock exchangeRateServiceMock;
+ public ExchangeRatesDTO ExchangeRateDTOTestData { get; set; } = new ExchangeRatesDTO();
+ public IEnumerable testExchangeRatesData { get; set; }
+ public IEnumerable exchangeRatesDTO { get; set; }
+ public IEnumerable testCurrenciesData { get; set; }
+
+ public ExchangeRateServiceTests()
+ {
+ testExchangeRatesData = new List()
+ {
+ new ExchangeRate(new Currency("CZK"), new Currency("USD"), 0.89m),
+ new ExchangeRate(new Currency("CZK"), new Currency("EUR"), 0.86m),
+ new ExchangeRate(new Currency("CZK"), new Currency("AUD"), 1.37m),
+ new ExchangeRate(new Currency("CZK"), new Currency("GBP"), 0.0075m),
+ };
+
+ exchangeRatesDTO = new List()
+ {
+ new ExchangeRateDTO { Amount = 100, CurrencyCode = "USD", Currency = "US Dollar", Country = "United States", Rate = 1.0m},
+ new ExchangeRateDTO { Amount = 200, CurrencyCode = "EUR", Currency = "Euro", Country = "European Union", Rate = 0.89m },
+ new ExchangeRateDTO { Amount = 300, CurrencyCode = "GBP", Currency = "British Pound", Country = "United Kingdom", Rate = 0.75m},
+ new ExchangeRateDTO { Amount = 1000, CurrencyCode = "JPY", Currency = "Japanese Yen", Country = "Japan", Rate = 109.65m},
+ };
+ ExchangeRateDTOTestData.Rates = exchangeRatesDTO;
+ testCurrenciesData = new List(TestingData.currencies);
+
+ exchangeRateServiceMock = new Mock();
+ exchangeRateServiceMock.Setup(s => s.GetExchangeRateAsync()).ReturnsAsync(testExchangeRatesData);
+ }
+
+
+ [Fact]
+ public async Task GetExchangeRates_NotNullAsync()
+ {
+ var result = await exchangeRateServiceMock.Object.GetExchangeRateAsync();
+
+ Assert.NotNull(result);
+ Assert.True(result.Count() > 0);
+ }
+ [Fact]
+
+ public async Task ConvertsExchangeRates_OkAsync()
+ {
+ var result = ConverterExtension.ToExchangeRates(ExchangeRateDTOTestData);
+
+ Assert.NotNull(result);
+ Assert.True(result.Count() > 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/jobs/Backend/Task/src/ExchangeRateUpdater.Tests/ExchangeRateUpdater.Tests.csproj b/jobs/Backend/Task/src/ExchangeRateUpdater.Tests/ExchangeRateUpdater.Tests.csproj
new file mode 100644
index 000000000..6dcbc0047
--- /dev/null
+++ b/jobs/Backend/Task/src/ExchangeRateUpdater.Tests/ExchangeRateUpdater.Tests.csproj
@@ -0,0 +1,30 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jobs/Backend/Task/src/Readme.md b/jobs/Backend/Task/src/Readme.md
new file mode 100644
index 000000000..98a3a8d39
--- /dev/null
+++ b/jobs/Backend/Task/src/Readme.md
@@ -0,0 +1,49 @@
+# Mews backend task - Exchange Rate Updater solution
+This is a repository with solution for Mews backend task. Said task is supposed to fetch exchange rates from Czech National banks for their national currency.
+## Requirements
+For sucessfull running of the solution certain packages are required:
+* Serilog - consists of several packages such as Serilog sinks for different kinds of outputs (console and file in this solution), Serilog hosting and configuration for configuring logging via appsettings json file
+* Microsoft.Extensions - extensions package that consists of different package needed for setting up of the program.cs class:
+ * Microsoft.Extensions.Caching.Memory - package for in memory caching of returned exchange rates
+ * Microsoft.Extensions.Configuration - package for binding appsettings json to a custom settings class
+ * Microsoft.Extensions.Logging - package for logger used in Serilog
+ * Microsoft.Extensions.Http - package for http client for sending get requests to the bank API
+## Logging
+Logging in this solution is done via Serilog which allows the user to setup a logger with just few lines of code. It allows the user to have logging done directly to different outpouts such as:
+* Message broker
+* Console
+* File
+
+```
+var config = new ConfigurationBuilder()
+ .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
+ .Build();
+Log.Logger = new LoggerConfiguration()
+ .ReadFrom.Configuration(config)
+ .CreateLogger();
+var host = new HostBuilder().UseSerilog();
+```
+## How To Run
+Use dotnet run to run the solution
+## Projects
+### [App](ExchangeRateUpdater.App)
+Main part of the project. Inside of it are [service](ExchangeRateUpdater.App/Services/ExchangeRateService.cs) and [provider](ExchangeRateUpdater.App/Providers/ExchangeRateProvider.cs) alongside their interfaces for the fetching of exchange rates.
+
+### [Domain](ExchangeRateUpdater.Domain)
+Consists of all supporting classes ExchangeRateUpdater.App requires such as configuration, DTO, model, etc classes.
+### [Tests](ExchangeRateUpdater.Tests)
+Consits of tests for ExchangeRateUpdater.App service and provider.
+
+## Configuration
+Configuration is stored in [appsettings.json](ExchangeRateUpdater.App/appsettings.json) file which is parsed inside [settings](ExchangeRateUpdater.Domain/Configurations/ExchangeRateProviderSettings.cs) file. It allows the user to enter the API endpoint where exchange rates can be found.
+
+## Testing
+
+Running dotnet test inside [Tests](ExchangeRateUpdater.Tests/) directory will execute the application tests.
+
+## Possible improvements
+* Caching - Caching could be implemented in Redis instead of in memory caching
+* Docker, CI/CD - Was not required in the task but it is a must have thing in every modern production environment
+* Performance tracing and metrics - Enterprise solutions require tracing of how solution is performing, Grafana could be used for creating easy to read dashboards for tracking.
+
+