Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
33eca2d
Refactor: migrate to modular project structure with separate domain, …
dieguezthiago Sep 17, 2025
ff33b1a
Add `ExchangeRateProvider` with dependency injection support and enha…
dieguezthiago Sep 18, 2025
9ab8929
Add caching support with `InMemoryCacheService` and `ExchangeRateProv…
dieguezthiago Sep 18, 2025
893de45
Add unit tests for `Currency` value object
dieguezthiago Sep 19, 2025
efdde0d
Add `DateTimeProvider` with dependency injection support
dieguezthiago Sep 20, 2025
7fb0506
Add `DateOnly` and `DateTime` extensions, refactor namespace structur…
dieguezthiago Sep 20, 2025
e414c5d
Update `DateTimeProvider` to use `DateTime` instead of `DateTimeOffset`
dieguezthiago Sep 20, 2025
c4bcd52
Add `CnbApiClientCacheDecorator` and `CnbApiClientDataUpdateCalculato…
dieguezthiago Sep 20, 2025
dac76c6
Add unit tests for `CnbApiClientCacheDecorator` and update solution s…
dieguezthiago Sep 20, 2025
563485d
Update namespace import for `ServiceCollectionExtensions` in `Program…
dieguezthiago Sep 20, 2025
731e286
Add unit tests for `CnbApiClientDataUpdateCalculator` in the `Exchang…
dieguezthiago Sep 20, 2025
cf540f4
Remove `ExchangeRateProviderCacheDecorator` and related service confi…
dieguezthiago Sep 20, 2025
e42c70d
Bind `CacheOptions` to configuration and update `AddInMemoryCache` me…
dieguezthiago Sep 21, 2025
c66710c
Add `Exchange.Application.UnitTests` project to solution and configur…
dieguezthiago Sep 21, 2025
c52f510
Refactor `CnbApiClientCacheDecoratorTests` to initialize mocks in-fie…
dieguezthiago Sep 21, 2025
35745b9
Update `ExchangeRate.ToString` to format `Value` with fixed three dec…
dieguezthiago Sep 21, 2025
3dab023
Add unit tests for `ExchangeRateProvider` and update test project dep…
dieguezthiago Sep 21, 2025
e81e7dd
Refactor unit tests to initialize SUT (`_sut`) in constructors for be…
dieguezthiago Sep 21, 2025
bb316fb
Refactor `ExchangeRateProviderTests` to initialize `_sut` in the cons…
dieguezthiago Sep 21, 2025
cd4b08e
Add `Currency.IDR` to supported currencies in `App.cs`
dieguezthiago Sep 21, 2025
ec57d83
Add validation for empty currency codes in `Currency.FromCode` and co…
dieguezthiago Sep 21, 2025
513d6ed
Refactor `App` to support dynamic currency input and enhance DI confi…
dieguezthiago Sep 21, 2025
d4af7b4
Add logging to `CnbApiClientCacheDecorator` and `CnbApiClientDataUpda…
dieguezthiago Sep 21, 2025
513542d
Enhance logging in `CnbApiClientCacheDecorator` for cache retrieval a…
dieguezthiago Sep 21, 2025
f46bccb
Refactor `Currency.FromCode` to use custom exceptions for invalid and…
dieguezthiago Sep 21, 2025
2ae4198
Add exception handling and request timing middlewares to improve API …
dieguezthiago Sep 21, 2025
59af8f9
Replace weather forecast logic with exchange rates endpoint, integrat…
dieguezthiago Sep 21, 2025
8861235
Update `CurrencyTests` to assert custom exceptions for invalid and un…
dieguezthiago Sep 21, 2025
3e10da9
Add Docker support with necessary files, update solution structure, a…
dieguezthiago Sep 21, 2025
e73e829
Add `docker-compose.yaml`, update Dockerfile and solution for multi-s…
dieguezthiago Sep 21, 2025
5177f4e
Add "Docs" solution folder and include `README.md` in solution structure
dieguezthiago Sep 21, 2025
6ccd0a6
Add metadata to the exchange rates endpoint with `WithName`, `Produce…
dieguezthiago Sep 21, 2025
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
81 changes: 58 additions & 23 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,23 +1,58 @@
!.gitkeep
!.gitignore
!*.dll
[Oo]bj
[Bb]in
*.user
*.suo
*.[Cc]ache
*.bak
*.ncb
*.DS_Store
*.userprefs
*.iml
*.ncrunch*
.*crunch*.local.xml
.idea
[Tt]humbs.db
*.tgz
*.sublime-*

node_modules
bower_components
npm-debug.log
## A streamlined .gitignore for modern .NET projects
## including temporary files, build results, and
## files generated by popular .NET tools. If you are
## developing with Visual Studio, the VS .gitignore
## https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
## has more thorough IDE-specific entries.
##
## Get latest from https://github.com/github/gitignore/blob/main/Dotnet.gitignore

# Rider
.idea/
*.DotSettings.user

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/

# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/

# ASP.NET Scaffolding
ScaffoldingReadMe.txt

# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg

# Others
~$*
*~
CodeCoverage/

# MSBuild Binary and Structured Log
*.binlog

# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
25 changes: 25 additions & 0 deletions jobs/Backend/Task/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
20 changes: 0 additions & 20 deletions jobs/Backend/Task/Currency.cs

This file was deleted.

47 changes: 47 additions & 0 deletions jobs/Backend/Task/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release

WORKDIR /src

COPY ./ExchangeRateUpdater.sln .
COPY ./Exchange.Api/ ./Exchange.Api/
COPY ./Exchange.Application/ ./Exchange.Application/
COPY ./Exchange.ConsoleApp/ ./Exchange.ConsoleApp/
COPY ./Exchange.Domain/ ./Exchange.Domain/
COPY ./Exchange.Infrastructure/ ./Exchange.Infrastructure/

COPY ./Exchange.Application.UnitTests ./Exchange.Application.UnitTests
COPY ./Exchange.Domain.UnitTests ./Exchange.Domain.UnitTests
COPY ./Exchange.Infrastructure.UnitTests ./Exchange.Infrastructure.UnitTests

RUN dotnet restore ExchangeRateUpdater.sln

COPY . .

# Build API
WORKDIR /src/Exchange.Api
RUN dotnet build -c $BUILD_CONFIGURATION -o /app/build

# Build ConsoleApp
WORKDIR /src/Exchange.ConsoleApp
RUN dotnet build -c $BUILD_CONFIGURATION -o /app/build

FROM build AS publish
ARG BUILD_CONFIGURATION=Release

WORKDIR /src/Exchange.Api
RUN dotnet publish -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

WORKDIR /src/Exchange.ConsoleApp
RUN dotnet publish -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .

CMD [ "dotnet", "Exchange.ConsoleApp.dll" ]
3 changes: 3 additions & 0 deletions jobs/Backend/Task/Exchange.Api/Dtos/ExchangeRateDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Exchange.Api.Dtos;

public record ExchangeRateDto(string SourceCurrency, string TargetCurrency, decimal Value);
19 changes: 19 additions & 0 deletions jobs/Backend/Task/Exchange.Api/Exchange.Api.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

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

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Exchange.Application\Exchange.Application.csproj" />
<ProjectReference Include="..\Exchange.Infrastructure\Exchange.Infrastructure.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Exchange.Domain.Abstractions.Exceptions;

namespace Exchange.Api.Middlewares;

public class ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
{
public async Task InvokeAsync(HttpContext context)
{
try
{
await next(context);
}
catch (BadRequestException ex)
{
logger.LogError(ex, "A bad request occurred");
context.Response.StatusCode = StatusCodes.Status400BadRequest;
context.Response.ContentType = "application/json";
var response = new
{
error = ex.Message
};
await context.Response.WriteAsJsonAsync(response);
}
catch (Exception ex)
{
logger.LogError(ex, "An unhandled exception occurred during request processing");

context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/json";

var response = new
{
error = "An internal server error occurred"
};

await context.Response.WriteAsJsonAsync(response);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Diagnostics;

namespace Exchange.Api.Middlewares;

public class RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
{
public async Task Invoke(HttpContext context)
{
var stopwatch = Stopwatch.StartNew();

await next(context);

stopwatch.Stop();

var elapsedMs = stopwatch.ElapsedMilliseconds;
var method = context.Request.Method;
var path = context.Request.Path;
var statusCode = context.Response.StatusCode;

logger.LogInformation(
"Request {Method} {Path} responded {StatusCode} in {Elapsed} ms",
method,
path,
statusCode,
elapsedMs
);
}
}
51 changes: 51 additions & 0 deletions jobs/Backend/Task/Exchange.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Exchange.Api.Dtos;
using Exchange.Api.Middlewares;
using Exchange.Application.Extensions;
using Exchange.Application.Services;
using Exchange.Domain.ValueObjects;
using Exchange.Infrastructure.Extensions.ServiceCollectionExtensions;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddDateTimeProvider();
builder.Services.AddCnbApiClient(builder.Configuration);
builder.Services.AddInMemoryCache(builder.Configuration);
builder.Services.AddExchangeRateProvider();

var app = builder.Build();

app.UseMiddleware<RequestTimingMiddleware>();
app.UseMiddleware<ExceptionHandlingMiddleware>();

app.UseSwagger();
app.UseSwaggerUI();

app.MapGet("/exchange-rates", async (
[FromServices] IExchangeRateProvider exchangeRateProvider,
[FromQuery] string[] currencyCodes,
CancellationToken cancellationToken
) =>
{
var requestedCurrencies = currencyCodes.Select(Currency.FromCode).ToList();

var exchangeRates = await exchangeRateProvider.GetExchangeRatesAsync(requestedCurrencies, cancellationToken);

return exchangeRates.Select(er =>
new ExchangeRateDto(
er.SourceCurrency.Code,
er.TargetCurrency.Code,
er.Value)
).ToList();
})
.WithName("GetExchangeRates")
.Produces<IEnumerable<ExchangeRateDto>>()
.Produces(StatusCodes.Status400BadRequest)
.WithOpenApi();

await app.RunAsync();
25 changes: 25 additions & 0 deletions jobs/Backend/Task/Exchange.Api/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5032",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7167;http://localhost:5032",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
8 changes: 8 additions & 0 deletions jobs/Backend/Task/Exchange.Api/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
17 changes: 17 additions & 0 deletions jobs/Backend/Task/Exchange.Api/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"CnbApi": {
"BaseAddress": "https://api.cnb.cz/",
"TimeoutInSeconds": 30
},
"Cache": {
"DefaultAbsoluteExpiration": "10"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"System.Net.Http.HttpClient": "Warning"
}
},
"AllowedHosts": "*"
}
Loading