diff --git a/jobs/Backend/Task/ExchangeRateProvider.cs b/jobs/Backend/Task/ExchangeRateProvider.cs index 6f82a97fb..7f10db30e 100644 --- a/jobs/Backend/Task/ExchangeRateProvider.cs +++ b/jobs/Backend/Task/ExchangeRateProvider.cs @@ -1,10 +1,23 @@ -using System.Collections.Generic; +using ExchangeRateProvider.Models; +using ExchangeRateUpdater.Interfaces; +using System; +using System.Collections.Generic; using System.Linq; namespace ExchangeRateUpdater { - public class ExchangeRateProvider - { + public class ExchangeRateProvider : IExchangeRateProvider + {/// + /// The data source to fetch exchange rates from. + /// + private BaseExchangeDataSource _exchangeDataSource; + + + public ExchangeRateProvider(BaseExchangeDataSource baseExchangeDataSource) + { + _exchangeDataSource = baseExchangeDataSource; + } + /// /// 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", @@ -13,7 +26,25 @@ public class ExchangeRateProvider /// public IEnumerable GetExchangeRates(IEnumerable currencies) { - return Enumerable.Empty(); + try + { + var textFormatUrl = _exchangeDataSource.GetExchangeRateDatasetUrl(); + + var readerService = ExchangeDataSourceReaderFactory.CreateDataSourceReader(_exchangeDataSource.DataSourceType); + // Fetch and parse the data + var exchangeRates = readerService.FetchAndParseExchangeRatesAsync(_exchangeDataSource) + .GetAwaiter() + .GetResult(); + + // Filter the exchange rates based on the specified currencies + return exchangeRates.Where(rate => + currencies.Any(c => c.Code == rate.TargetCurrency.Code)); + } + catch (Exception ex) + { + // return a specific error + throw new Exception("An error occurred while fetching exchange rates.", ex); + } } } } diff --git a/jobs/Backend/Task/ExchangeRateUpdater.csproj b/jobs/Backend/Task/ExchangeRateUpdater.csproj index 2fc654a12..d9d8c82d6 100644 --- a/jobs/Backend/Task/ExchangeRateUpdater.csproj +++ b/jobs/Backend/Task/ExchangeRateUpdater.csproj @@ -5,4 +5,8 @@ net6.0 + + + + \ No newline at end of file diff --git a/jobs/Backend/Task/Interfaces/IExchangeDataSourceFactory.cs b/jobs/Backend/Task/Interfaces/IExchangeDataSourceFactory.cs new file mode 100644 index 000000000..a4862c270 --- /dev/null +++ b/jobs/Backend/Task/Interfaces/IExchangeDataSourceFactory.cs @@ -0,0 +1,9 @@ +using ExchangeRateProvider.Models; + +namespace ExchangeRateUpdater.Interfaces +{ + public interface IExchangeDataSourceFactory + { + public BaseExchangeDataSource CreateDataSource(ExchangeRateDataSourceType dataSourceType); + } +} diff --git a/jobs/Backend/Task/Interfaces/IExchangeRateProvider.cs b/jobs/Backend/Task/Interfaces/IExchangeRateProvider.cs new file mode 100644 index 000000000..0ac260b67 --- /dev/null +++ b/jobs/Backend/Task/Interfaces/IExchangeRateProvider.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace ExchangeRateUpdater.Interfaces +{ + public interface IExchangeRateProvider + { + public IEnumerable GetExchangeRates(IEnumerable currencies); + } +} diff --git a/jobs/Backend/Task/Models/BaseExchangeDataSource.cs b/jobs/Backend/Task/Models/BaseExchangeDataSource.cs new file mode 100644 index 000000000..1fffa0299 --- /dev/null +++ b/jobs/Backend/Task/Models/BaseExchangeDataSource.cs @@ -0,0 +1,34 @@ +namespace ExchangeRateProvider.Models +{ + public abstract class BaseExchangeDataSource + { + /// + /// The source currency code. + /// + public abstract SourceCurrencyCode SourceCurrencyCode { get; } + + /// + /// The type of the exchange rate data source. + /// + public abstract ExchangeRateDataSourceType DataSourceType { get; } + + /// + /// The URL to connect to the exchange rate data source. + /// + public abstract string ConnectionUrl { get; } + + /// + /// Returns the URL to fetch the exchange rate dataset. + /// + /// + public abstract string GetExchangeRateDatasetUrl(); + } + + /// + /// The source currency code. + /// + public enum SourceCurrencyCode + { + CZK = 1 + } +} diff --git a/jobs/Backend/Task/Models/CnbExchangeRateDataSource.cs b/jobs/Backend/Task/Models/CnbExchangeRateDataSource.cs new file mode 100644 index 000000000..ae8f8da13 --- /dev/null +++ b/jobs/Backend/Task/Models/CnbExchangeRateDataSource.cs @@ -0,0 +1,68 @@ +using System; + +namespace ExchangeRateProvider.Models +{ + public class CnbExchangeRateDataSource : BaseExchangeDataSource + { + /// + /// The base URL of the exchange rate data source. + /// + private const string BaseUrl = "https://www.cnb.cz"; + + /// + /// The URL path to fetch the exchange rate dataset. + /// + private const string DailyTextUrl = "/en/financial-markets/foreign-exchange-market/central-bank-exchange-rate-fixing/central-bank-exchange-rate-fixing/daily.txt"; + + /// + /// The date of the exchange rate. Default is today. + /// + private DateTime _exchangeRateDate; + + /// + /// The date format. + /// + private string DATE_FORMAT = "dd.MM.yyyy"; + + /// + /// The type of the exchange rate data source. + /// + public override ExchangeRateDataSourceType DataSourceType => ExchangeRateDataSourceType.Cnb; + + /// + /// The URL to connect to the exchange rate data source. + /// + public override string ConnectionUrl => $"{BaseUrl}{DailyTextUrl}"; + + /// + /// The date of the exchange rate. + /// + public DateTime ExchangeRateDate { get => _exchangeRateDate; set => _exchangeRateDate = value; } + + /// + /// The source currency code. + /// + public override SourceCurrencyCode SourceCurrencyCode => SourceCurrencyCode.CZK; + + + public CnbExchangeRateDataSource(DateTime? exchangeRateDate = null) : base() + { + ExchangeRateDate = exchangeRateDate ?? DateTime.UtcNow; + } + + /// + /// Returns the URL to fetch the exchange rate dataset. + /// + /// + public override string GetExchangeRateDatasetUrl() + { + var formattedDate = ExchangeRateDate.ToString(DATE_FORMAT); + return $"{ConnectionUrl}?date={formattedDate}"; + } + + public override string ToString() + { + return "Czech National Bank (CNB)"; + } + } +} diff --git a/jobs/Backend/Task/Models/ExchangeDataSourceFactory.cs b/jobs/Backend/Task/Models/ExchangeDataSourceFactory.cs new file mode 100644 index 000000000..67ee53036 --- /dev/null +++ b/jobs/Backend/Task/Models/ExchangeDataSourceFactory.cs @@ -0,0 +1,33 @@ +using ExchangeRateUpdater.Interfaces; +using System; + +namespace ExchangeRateProvider.Models +{ + public class ExchangeDataSourceFactory : IExchangeDataSourceFactory + { + /// + /// Creates a new instance of the exchange rate data source. + /// + /// + /// + /// + public BaseExchangeDataSource CreateDataSource(ExchangeRateDataSourceType dataSourceType) + { + switch (dataSourceType) + { + case ExchangeRateDataSourceType.Cnb: + return new CnbExchangeRateDataSource(); + default: + throw new NotSupportedException($"Data source type '{dataSourceType}' is not supported."); + } + } + } + + /// + /// The type of the exchange rate data source. + /// + public enum ExchangeRateDataSourceType + { + Cnb = 1 + } +} diff --git a/jobs/Backend/Task/Models/ExchangeDataSourceReaderFactory.cs b/jobs/Backend/Task/Models/ExchangeDataSourceReaderFactory.cs new file mode 100644 index 000000000..902426d6b --- /dev/null +++ b/jobs/Backend/Task/Models/ExchangeDataSourceReaderFactory.cs @@ -0,0 +1,22 @@ +using ExchangeRateProvider.Services; +using System; + +namespace ExchangeRateProvider.Models +{ + /// + /// The factory for creating the exchange rate data source reader. + /// + public class ExchangeDataSourceReaderFactory + { + public static BaseExchangeDataSourceReader CreateDataSourceReader(ExchangeRateDataSourceType dataSourceType) + { + switch (dataSourceType) + { + case ExchangeRateDataSourceType.Cnb: + return new CnbExchangeDataSourceReader(); + default: + throw new NotSupportedException($"Data source type '{dataSourceType}' is not supported."); + } + } + } +} diff --git a/jobs/Backend/Task/Program.cs b/jobs/Backend/Task/Program.cs index 379a69b1f..a5ae87517 100644 --- a/jobs/Backend/Task/Program.cs +++ b/jobs/Backend/Task/Program.cs @@ -1,4 +1,7 @@ -using System; +using ExchangeRateProvider.Models; +using ExchangeRateUpdater.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using System; using System.Collections.Generic; using System.Linq; @@ -23,7 +26,20 @@ public static void Main(string[] args) { try { - var provider = new ExchangeRateProvider(); + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddTransient(); + serviceCollection.AddTransient(provider => + { + var factory = provider.GetRequiredService(); + return new ExchangeRateProvider(factory.CreateDataSource(ExchangeRateDataSourceType.Cnb)); + + }); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var provider = serviceProvider.GetRequiredService(); + var rates = provider.GetExchangeRates(currencies); Console.WriteLine($"Successfully retrieved {rates.Count()} exchange rates:"); @@ -32,6 +48,10 @@ public static void Main(string[] args) Console.WriteLine(rate.ToString()); } } + catch (NotSupportedException e) + { + Console.WriteLine($"Could not create data source: '{e.Message}'."); + } catch (Exception e) { Console.WriteLine($"Could not retrieve exchange rates: '{e.Message}'."); diff --git a/jobs/Backend/Task/Services/BaseExchangeDataSourceReader.cs b/jobs/Backend/Task/Services/BaseExchangeDataSourceReader.cs new file mode 100644 index 000000000..71a405b92 --- /dev/null +++ b/jobs/Backend/Task/Services/BaseExchangeDataSourceReader.cs @@ -0,0 +1,23 @@ +using ExchangeRateProvider.Models; +using ExchangeRateUpdater; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ExchangeRateProvider.Services +{ + public abstract class BaseExchangeDataSourceReader + { + /// + /// Fetches and parses the exchange rates from the data source. + /// + protected BaseExchangeDataSource _baseExchangeDataSource; + + /// + /// Fetches and parses the exchange rates from the data source. + /// + /// + /// + public abstract Task> FetchAndParseExchangeRatesAsync(BaseExchangeDataSource baseExchangeDataSource); + + } +} diff --git a/jobs/Backend/Task/Services/CnbExchangeDataSourceReader.cs b/jobs/Backend/Task/Services/CnbExchangeDataSourceReader.cs new file mode 100644 index 000000000..9c7fa89a4 --- /dev/null +++ b/jobs/Backend/Task/Services/CnbExchangeDataSourceReader.cs @@ -0,0 +1,58 @@ +using ExchangeRateProvider.Models; +using ExchangeRateUpdater; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace ExchangeRateProvider.Services +{ + public class CnbExchangeDataSourceReader : BaseExchangeDataSourceReader + { + /// + /// Fetches and parses the exchange rates from the data source. + /// + /// + /// + public override async Task> FetchAndParseExchangeRatesAsync(BaseExchangeDataSource baseExchangeDataSource) + { + _baseExchangeDataSource = baseExchangeDataSource; + + var httpClient = new HttpClient(); + var response = await httpClient.GetStringAsync(baseExchangeDataSource.GetExchangeRateDatasetUrl()); + + return ParseExchangeRates(response); + } + + /// + /// Parses the exchange rates from the text data. + /// + /// + /// + private List ParseExchangeRates(string textData) + { + var exchangeRates = new List(); + var lines = textData.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + + // Skip the first two lines (date and header) + foreach (var line in lines.Skip(2)) + { + var parts = line.Split('|'); + if (parts.Length >= 5) + { + var currencyCode = parts[3]; + var amount = int.Parse(parts[2]); // Amount indicates the base unit (e.g., 100 HUF) + if (decimal.TryParse(parts[4], out var rate)) + { + var sourceCurrency = new Currency(_baseExchangeDataSource.SourceCurrencyCode.ToString()); + var targetCurrency = new Currency(currencyCode); + exchangeRates.Add(new ExchangeRate(sourceCurrency, targetCurrency, rate / amount)); + } + } + } + return exchangeRates; + } + } +}