Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ExchangeRateProvider]:: ExchangeRateProvider service implementation #660

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions jobs/Backend/Task/ExchangeRateProvider.cs
Original file line number Diff line number Diff line change
@@ -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
{/// <summary>
/// The data source to fetch exchange rates from.
/// </summary>
private BaseExchangeDataSource _exchangeDataSource;


public ExchangeRateProvider(BaseExchangeDataSource baseExchangeDataSource)
{
_exchangeDataSource = baseExchangeDataSource;
}

/// <summary>
/// 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",
Expand All @@ -13,7 +26,25 @@ public class ExchangeRateProvider
/// </summary>
public IEnumerable<ExchangeRate> GetExchangeRates(IEnumerable<Currency> currencies)
{
return Enumerable.Empty<ExchangeRate>();
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);
}
}
}
}
4 changes: 4 additions & 0 deletions jobs/Backend/Task/ExchangeRateUpdater.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
</ItemGroup>

</Project>
9 changes: 9 additions & 0 deletions jobs/Backend/Task/Interfaces/IExchangeDataSourceFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using ExchangeRateProvider.Models;

namespace ExchangeRateUpdater.Interfaces
{
public interface IExchangeDataSourceFactory
{
public BaseExchangeDataSource CreateDataSource(ExchangeRateDataSourceType dataSourceType);
}
}
9 changes: 9 additions & 0 deletions jobs/Backend/Task/Interfaces/IExchangeRateProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Collections.Generic;

namespace ExchangeRateUpdater.Interfaces
{
public interface IExchangeRateProvider
{
public IEnumerable<ExchangeRate> GetExchangeRates(IEnumerable<Currency> currencies);
}
}
34 changes: 34 additions & 0 deletions jobs/Backend/Task/Models/BaseExchangeDataSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace ExchangeRateProvider.Models
{
public abstract class BaseExchangeDataSource
{
/// <summary>
/// The source currency code.
/// </summary>
public abstract SourceCurrencyCode SourceCurrencyCode { get; }

/// <summary>
/// The type of the exchange rate data source.
/// </summary>
public abstract ExchangeRateDataSourceType DataSourceType { get; }

/// <summary>
/// The URL to connect to the exchange rate data source.
/// </summary>
public abstract string ConnectionUrl { get; }

/// <summary>
/// Returns the URL to fetch the exchange rate dataset.
/// </summary>
/// <returns></returns>
public abstract string GetExchangeRateDatasetUrl();
}

/// <summary>
/// The source currency code.
/// </summary>
public enum SourceCurrencyCode
{
CZK = 1
}
}
68 changes: 68 additions & 0 deletions jobs/Backend/Task/Models/CnbExchangeRateDataSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;

namespace ExchangeRateProvider.Models
{
public class CnbExchangeRateDataSource : BaseExchangeDataSource
{
/// <summary>
/// The base URL of the exchange rate data source.
/// </summary>
private const string BaseUrl = "https://www.cnb.cz";

/// <summary>
/// The URL path to fetch the exchange rate dataset.
/// </summary>
private const string DailyTextUrl = "/en/financial-markets/foreign-exchange-market/central-bank-exchange-rate-fixing/central-bank-exchange-rate-fixing/daily.txt";

/// <summary>
/// The date of the exchange rate. Default is today.
/// </summary>
private DateTime _exchangeRateDate;

/// <summary>
/// The date format.
/// </summary>
private string DATE_FORMAT = "dd.MM.yyyy";

/// <summary>
/// The type of the exchange rate data source.
/// </summary>
public override ExchangeRateDataSourceType DataSourceType => ExchangeRateDataSourceType.Cnb;

/// <summary>
/// The URL to connect to the exchange rate data source.
/// </summary>
public override string ConnectionUrl => $"{BaseUrl}{DailyTextUrl}";

/// <summary>
/// The date of the exchange rate.
/// </summary>
public DateTime ExchangeRateDate { get => _exchangeRateDate; set => _exchangeRateDate = value; }

/// <summary>
/// The source currency code.
/// </summary>
public override SourceCurrencyCode SourceCurrencyCode => SourceCurrencyCode.CZK;


public CnbExchangeRateDataSource(DateTime? exchangeRateDate = null) : base()
{
ExchangeRateDate = exchangeRateDate ?? DateTime.UtcNow;
}

/// <summary>
/// Returns the URL to fetch the exchange rate dataset.
/// </summary>
/// <returns></returns>
public override string GetExchangeRateDatasetUrl()
{
var formattedDate = ExchangeRateDate.ToString(DATE_FORMAT);
return $"{ConnectionUrl}?date={formattedDate}";
}

public override string ToString()
{
return "Czech National Bank (CNB)";
}
}
}
33 changes: 33 additions & 0 deletions jobs/Backend/Task/Models/ExchangeDataSourceFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using ExchangeRateUpdater.Interfaces;
using System;

namespace ExchangeRateProvider.Models
{
public class ExchangeDataSourceFactory : IExchangeDataSourceFactory
{
/// <summary>
/// Creates a new instance of the exchange rate data source.
/// </summary>
/// <param name="dataSourceType"></param>
/// <returns></returns>
/// <exception cref="NotSupportedException"></exception>
public BaseExchangeDataSource CreateDataSource(ExchangeRateDataSourceType dataSourceType)
{
switch (dataSourceType)
{
case ExchangeRateDataSourceType.Cnb:
return new CnbExchangeRateDataSource();
default:
throw new NotSupportedException($"Data source type '{dataSourceType}' is not supported.");
}
}
}

/// <summary>
/// The type of the exchange rate data source.
/// </summary>
public enum ExchangeRateDataSourceType
{
Cnb = 1
}
}
22 changes: 22 additions & 0 deletions jobs/Backend/Task/Models/ExchangeDataSourceReaderFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using ExchangeRateProvider.Services;
using System;

namespace ExchangeRateProvider.Models
{
/// <summary>
/// The factory for creating the exchange rate data source reader.
/// </summary>
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.");
}
}
}
}
24 changes: 22 additions & 2 deletions jobs/Backend/Task/Program.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -23,7 +26,20 @@ public static void Main(string[] args)
{
try
{
var provider = new ExchangeRateProvider();
var serviceCollection = new ServiceCollection();

serviceCollection.AddTransient<IExchangeDataSourceFactory, ExchangeDataSourceFactory>();
serviceCollection.AddTransient<IExchangeRateProvider>(provider =>
{
var factory = provider.GetRequiredService<IExchangeDataSourceFactory>();
return new ExchangeRateProvider(factory.CreateDataSource(ExchangeRateDataSourceType.Cnb));

});

var serviceProvider = serviceCollection.BuildServiceProvider();

var provider = serviceProvider.GetRequiredService<IExchangeRateProvider>();

var rates = provider.GetExchangeRates(currencies);

Console.WriteLine($"Successfully retrieved {rates.Count()} exchange rates:");
Expand All @@ -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}'.");
Expand Down
23 changes: 23 additions & 0 deletions jobs/Backend/Task/Services/BaseExchangeDataSourceReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using ExchangeRateProvider.Models;
using ExchangeRateUpdater;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ExchangeRateProvider.Services
{
public abstract class BaseExchangeDataSourceReader
{
/// <summary>
/// Fetches and parses the exchange rates from the data source.
/// </summary>
protected BaseExchangeDataSource _baseExchangeDataSource;

/// <summary>
/// Fetches and parses the exchange rates from the data source.
/// </summary>
/// <param name="baseExchangeDataSource"></param>
/// <returns></returns>
public abstract Task<List<ExchangeRate>> FetchAndParseExchangeRatesAsync(BaseExchangeDataSource baseExchangeDataSource);

}
}
58 changes: 58 additions & 0 deletions jobs/Backend/Task/Services/CnbExchangeDataSourceReader.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Fetches and parses the exchange rates from the data source.
/// </summary>
/// <param name="baseExchangeDataSource"></param>
/// <returns></returns>
public override async Task<List<ExchangeRate>> FetchAndParseExchangeRatesAsync(BaseExchangeDataSource baseExchangeDataSource)
{
_baseExchangeDataSource = baseExchangeDataSource;

var httpClient = new HttpClient();
var response = await httpClient.GetStringAsync(baseExchangeDataSource.GetExchangeRateDatasetUrl());

return ParseExchangeRates(response);
}

/// <summary>
/// Parses the exchange rates from the text data.
/// </summary>
/// <param name="textData"></param>
/// <returns></returns>
private List<ExchangeRate> ParseExchangeRates(string textData)
{
var exchangeRates = new List<ExchangeRate>();
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;
}
}
}