Skip to content
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
20 changes: 0 additions & 20 deletions jobs/Backend/Task/Currency.cs

This file was deleted.

23 changes: 0 additions & 23 deletions jobs/Backend/Task/ExchangeRate.cs

This file was deleted.

19 changes: 0 additions & 19 deletions jobs/Backend/Task/ExchangeRateProvider.cs

This file was deleted.

5 changes: 5 additions & 0 deletions jobs/Backend/Task/ExchangeRateProvider/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
32 changes: 32 additions & 0 deletions jobs/Backend/Task/ExchangeRateProvider/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project>
<!-- Enable Central Package Management -->
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<!-- Define all package versions centrally -->
<ItemGroup>
<!-- ASP.NET Core -->
<PackageVersion Include="LazyCache" Version="2.4.0" />
<PackageVersion Include="LazyCache.AspNetCore" Version="2.4.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<!-- Microsoft Extensions -->
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.Testing" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.0" />
<!-- Validation -->
<PackageVersion Include="FluentValidation" Version="12.1.1" />
<PackageVersion Include="Microsoft.Extensions.Http.Polly" Version="10.0.0" />
<PackageVersion Include="Polly" Version="8.6.5" />
<!-- OpenAPI/Swagger -->
<PackageVersion Include="Scalar.AspNetCore" Version="2.11.1" />
<!-- Testing -->
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<!-- Test Utilities -->
<PackageVersion Include="FakeItEasy" Version="8.3.0" />
<PackageVersion Include="Shouldly" Version="4.3.0" />
</ItemGroup>
</Project>
15 changes: 15 additions & 0 deletions jobs/Backend/Task/ExchangeRateProvider/ExchangeRateProvider.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/ExchangeRateProvider.Api/ExchangeRateProvider.Api.csproj" Id="3dc61bde-b5c0-4f6c-9fdb-65c3f6aeaefe" />
<Project Path="src/ExchangeRateProvider.Application/ExchangeRateProvider.Application.csproj" />
<Project Path="src/ExchangeRateProvider.Domain/ExchangeRateProvider.Domain.csproj" />
<Project Path="src/ExchangeRateProvider.Infrastructure/ExchangeRateProvider.Infrastructure.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/ExchangeRateProvider.Api.Tests.Integration/ExchangeRateProvider.Api.Tests.Integration.csproj" Id="2cbacfea-04c1-43a5-99ad-92cf11a52338" />
<Project Path="tests/ExchangeRateProvider.Api.Tests.Unit/ExchangeRateProvider.Api.Tests.Unit.csproj" Id="9826e788-8ef0-4f9b-980c-cf88a4ea982f" />
<Project Path="tests/ExchangeRateProvider.Application.Tests.Unit/ExchangeRateProvider.Application.Tests.Unit.csproj" Id="3a177543-1c5a-492d-8518-77cdae1f9d6f" />
<Project Path="tests/ExchangeRateProvider.Domain.Tests.Unit/ExchangeRateProvider.Domain.Tests.Unit.csproj" Id="da27cc0d-dd68-405d-8eb7-8e6203c6cc99" />
<Project Path="tests/ExchangeRateProvider.Infrastructure.Tests.Unit/ExchangeRateProvider.Infrastructure.Tests.Unit.csproj" Id="49b818ee-fd17-49ff-bbfa-cb9ead1b4fd0" />
</Folder>
</Solution>
191 changes: 191 additions & 0 deletions jobs/Backend/Task/ExchangeRateProvider/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# Exchange Rate Provider API

A REST API for retrieving exchange rates for a given currency with optional filtering, built with .NET 10, following clean architecture principles and SOLID design patterns.

## Architecture

The solution follows **Clean Architecture** with clear separation of concerns:

```
┌─────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ ExchangeRateProvider.Api │
│ (Controllers, Validators, OpenAPI Config) │
└────────────────────┬────────────────────────────────────┘
┌────────────────────▼────────────────────────────────────┐
│ Application Layer │
│ ExchangeRateProvider.Application │
│ (Handlers, Queries, DTOs, CQRS) │
└────────────────────┬────────────────────────────────────┘
┌────────────────────▼────────────────────────────────────┐
│ Domain Layer │
│ ExchangeRateProvider.Domain │
│ (Entities, Value Objects, Interfaces, Constants) │
└────────────────────┬────────────────────────────────────┘
┌────────────────────▼────────────────────────────────────┐
│ Infrastructure Layer │
│ ExchangeRateProvider.Infrastructure │
│ (External Services, HTTP Clients, Polly Policies) │
└─────────────────────────────────────────────────────────┘
```

## Getting Started

### Prerequisites

- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
- [Docker](https://www.docker.com/get-started) (optional, for containerized deployment)

### Building the Solution

```bash
# Restore dependencies
dotnet restore

# Build all projects
dotnet build
```

### Running Locally

```bash
# Run the API
cd src/ExchangeRateProvider.Api
dotnet run

# API will be available at:
# - HTTP: http://localhost:5291
# - HTTPS: https://localhost:7291
```

### Running Tests

```bash
# Run all tests
dotnet test
```

## Docker

### Building the Docker Image

```bash
# Build from solution root
docker build -t exchange-rate-provider-api -f src/ExchangeRateProvider.Api/Dockerfile .
```

### Running the Container

```bash
# Run on port 8080
docker run -p 8080:8080 --name exchange-rate-api exchange-rate-provider-api
```

## API Documentation

### Accessing API Documentation

When running in **Development** mode, interactive API documentation is available via **Scalar**:

```
https://localhost:5291/scalar/v1
```

### API Endpoints

#### Get Exchange Rates

**Endpoint**: `GET v1/api/exchange-rates`

**Query Parameters**:
- `baseCurrency` (required): The base currency code (e.g., "CZK")
- `quoteCurrencies` (optional): List of quote currency codes to filter results

**Example Requests**:

```bash
# Get all available exchange rates for CZK
curl "http://localhost:8080/v1/api/exchangerates?baseCurrency=CZK"

# Get specific quote currencies
curl "http://localhost:8080/v1/api/exchangerates?baseCurrency=CZK&quoteCurrencies=EUR&quoteCurrencies=USD&quoteCurrencies=GBP"
```

**Example Response** (200 OK):

```json
[
{
"baseCurrency": {
"code": "CZK"
},
"quoteCurrency": {
"code": "EUR"
},
"rate": 0.04012
},
{
"baseCurrency": {
"code": "CZK"
},
"quoteCurrency": {
"code": "USD"
},
"rate": 0.04357
}
]
```

**Error Responses**:

- **400 Bad Request**: Invalid or unsupported currency
```json
{
"title": "Invalid base currency",
"detail": "Base currency is required.",
"status": 400
}
```

- **400 Bad Request**: Unsupported currency
```json
{
"title": "Unsupported base currency",
"detail": "The currency 'XXX' is not supported. Supported currencies: CZK",
"status": 400
}
```

### Supported Currencies

**Base Currency**: Currently supports `CZK` (Czech Koruna)

**Quote Currencies**: All currencies provided by the CNB API, typically including:
- EUR (Euro)
- USD (US Dollar)
- GBP (British Pound)
- JPY (Japanese Yen)
- And 30+ other major world currencies

The API returns only currencies provided by the source - no calculated inverse rates.

## Configuration

### Application Settings

Configuration is managed via `appsettings.json` and `appsettings.Development.json`:

```json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;
using System.Diagnostics.CodeAnalysis;

namespace ExchangeRateProvider.Api.Configuration
{
[ExcludeFromCodeCoverage(Justification = "OpenAPI configuration with only metadata assignment - no testable logic")]
public class OpenApiDocumentTransformer : IOpenApiDocumentTransformer
{
public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
{
document.Info = new OpenApiInfo
{
Title = "Exchange Rate Provider API",
Version = "v1",
Description = "API for retrieving exchange rates for the given currency." +
"Provides current exchange rates and supports filtering by quote currencies.",
Contact = new OpenApiContact
{
Name = "API Support",
Email = "[email protected]"
}
};

return Task.CompletedTask;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Diagnostics.CodeAnalysis;

namespace ExchangeRateProvider.Api.Constants;

[ExcludeFromCodeCoverage(Justification = "Constants class with only compile-time string values - no executable logic")]
public static class ApiEndpoints
{
public const string ApiVersion = "v1";
private const string ApiBase = $"{ApiVersion}/api";

public static class ExchangeRates
{
public const string Base = $"{ApiBase}/exchange-rates";
public const string GetByCurrency = Base;
}
}
Loading