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

DotNet Backend Task: Exchange rates - aseverinac #662

Open
wants to merge 9 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@
node_modules
bower_components
npm-debug.log
.vs/
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using ExchangeRateUpdater.Core.Models;
using ExchangeRateUpdater.Core.Services.Abstractions;
using Microsoft.AspNetCore.Mvc;

namespace ExchangeRateUpdater.API.Controllers
{
[ApiController]
[Route("[controller]")]
public class ExchangeRateController : ControllerBase
{
private readonly IExchangeRateProvider _exchangeRateProvider;
private readonly ILogger<ExchangeRateController> _logger;

public ExchangeRateController(
IExchangeRateProvider exchangeRateProvider,
ILogger<ExchangeRateController> logger)
{
_exchangeRateProvider = exchangeRateProvider;
_logger = logger;
}

/// <summary>
/// Gets exchange rates for the specified currencies or all exchange rates if no currencies are specified.
/// </summary>
/// <param name="currencies">A list of currency codes to get exchange rates for. If not specified, all exchange rates are returned.</param>
/// <returns>A list of exchange rates.</returns>
/// <response code="200">Returns the list of exchange rates.</response>
/// <response code="204">No exchange rates available for the specified currencies.</response>
/// <response code="503">Error occurred while fetching exchange rates from the external service.</response>
/// <response code="500">An unexpected error occurred.</response>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<string>), StatusCodes.Status200OK)]
public IActionResult Get([FromQuery] List<string>? currencies)
{
IEnumerable<ExchangeRate> result;
try
{
if (currencies == null || currencies.Count == 0)
{
_logger.LogInformation("No currencies provided, fetching all exchange rates.");
currencies = new List<string>();
}
else
{
_logger.LogInformation("Fetching exchange rates for specified currencies.");
}

var currencyObjects = currencies.Select(c => new Currency(c)).ToList();
result = _exchangeRateProvider.GetExchangeRates(currencyObjects);
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Error occurred while fetching exchange rates from the external service.");
return StatusCode(StatusCodes.Status503ServiceUnavailable, "Error occurred while fetching exchange rates from the external service.");
}
catch (Exception ex)
{
_logger.LogError(ex, "An unexpected error occurred.");
return StatusCode(StatusCodes.Status500InternalServerError, "An unexpected error occurred.");
}

if (!result.Any())
{
return NoContent();
}

return Ok(result.Select(x => x.ToString()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.1.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ExchangeRateUpdater.Core\ExchangeRateUpdater.Core.csproj" />
</ItemGroup>

</Project>
51 changes: 51 additions & 0 deletions jobs/Backend/Task/ExchangeRateUpdater.API/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using ExchangeRateUpdater.Core.Models.Configuration;
using ExchangeRateUpdater.Core.Services;
using ExchangeRateUpdater.Core.Services.Abstractions;
using Microsoft.OpenApi.Models;
using System.Reflection;

var builder = WebApplication.CreateBuilder(args);

// Build configuration, include appsettings.json
builder.Configuration
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);

// Register services for dependency injection
builder.Services
.AddSingleton<IExchangeRateProvider, CnbExchangeRateProvider>()
.Configure<ExchangeRateSettings>(builder.Configuration.GetSection("ExchangeRateSettings"))
.AddLogging(configure => configure
.AddConsole() // Log to console for simplicity
.SetMinimumLevel(LogLevel.Information))
.AddMemoryCache()
.AddHttpClient<IExchangeRateProvider, CnbExchangeRateProvider>();

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Exchange Rate API", Version = "v1" });

// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

await app.RunAsync();
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:9208",
"sslPort": 44361
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5298",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7010;http://localhost:5298",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
14 changes: 14 additions & 0 deletions jobs/Backend/Task/ExchangeRateUpdater.API/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ExchangeRateSettings": {
"CnbApiUrl": "https://api.cnb.cz/cnbapi/exrates/daily",
"BaseCurrency": "CZK",
"CacheDurationInMinutes": 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ExchangeRateUpdater.Core\ExchangeRateUpdater.Core.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
67 changes: 67 additions & 0 deletions jobs/Backend/Task/ExchangeRateUpdater.Console/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using ExchangeRateUpdater.Core.Models.Configuration;
using ExchangeRateUpdater.Core.Services;
using ExchangeRateUpdater.Core.Services.Abstractions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.Linq;

namespace ExchangeRateUpdater.ConsoleApp
{
public static class Program
{
/// <summary>
/// Main console app for testing the implementation of the exchange rate provider.
/// It uses the exchange rate provider to retrieve exchange rates for a list of currencies and prints them to the console.
/// </summary>
public static void Main(string[] args)
{
var serviceProvider = SetupDependencies();
var provider = serviceProvider.GetService<IExchangeRateProvider>();

try
{
var rates = provider.GetExchangeRates(TestData.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();
}

/// <summary>
/// Setup dependencies for the console app.
/// </summary>
private static ServiceProvider SetupDependencies()
{
// Build configuration, incude appsettings.json
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();

// Register services for dependency injection
var serviceProvider = new ServiceCollection()
.AddSingleton<IExchangeRateProvider, CnbExchangeRateProvider>()
.Configure<ExchangeRateSettings>(configuration.GetSection("ExchangeRateSettings"))
.AddLogging(configure => configure
.AddConsole() // Log to console for simplicity
.SetMinimumLevel(LogLevel.Warning))
.AddMemoryCache()
.AddHttpClient<IExchangeRateProvider, CnbExchangeRateProvider>()
.Services.BuildServiceProvider();

return serviceProvider;
}
}
}
21 changes: 21 additions & 0 deletions jobs/Backend/Task/ExchangeRateUpdater.Console/TestData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using ExchangeRateUpdater.Core.Models;
using System.Collections.Generic;

namespace ExchangeRateUpdater.ConsoleApp
{
internal static class TestData
{
internal static readonly IEnumerable<Currency> Currencies =
[
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")
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"ExchangeRateSettings": {
"CnbApiUrl": "https://api.cnb.cz/cnbapi/exrates/daily",
"BaseCurrency": "CZK",
"CacheDurationInMinutes": 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Text.Json.Serialization;

namespace ExchangeRateUpdater.Core.Models.CnbApiResponse
{
/// <summary>
/// Class to describe exchange rates in the response from the CNB API.
/// </summary>
public class CnbExchangeRate
{
[JsonPropertyName("validFor")]
public DateTime ValidFor { get; set; }

[JsonPropertyName("order")]
public int Order { get; set; }

[JsonPropertyName("country")]
public string Country { get; set; } = null!;

[JsonPropertyName("currency")]
public string Currency { get; set; } = null!;

[JsonPropertyName("amount")]
public int Amount { get; set; }

[JsonPropertyName("currencyCode")]
public string CurrencyCode { get; set; } = null!;

[JsonPropertyName("rate")]
public decimal Rate { get; set; }
}
}
Loading