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.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace EchangeRateUpdater.Api.Configurations.Extensions;

using Scalar.AspNetCore;

public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseApiDocumentation(this WebApplication app)
{
var enableApiDocumentation = app.Configuration.GetValue<bool>("ApiDocumentation:Enabled", false);
if (!enableApiDocumentation)
{
return app;
}

app.MapOpenApi();
app.MapScalarApiReference((options, _) =>
{
options
.AddPreferredSecuritySchemes("Bearer")
.WithTitle("Exchange Rates Updater API")
.WithDarkMode(false)
.WithLayout(ScalarLayout.Classic)
.WithDefaultHttpClient(ScalarTarget.Shell, ScalarClient.Curl)
.WithTheme(ScalarTheme.Kepler)
.WithModels(false)
.WithDefaultOpenAllTags(false);
});

return app;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace EchangeRateUpdater.Api.Configurations.Extensions;

using Microsoft.OpenApi.Models;

public static class OpenApiConfiguration
{
public static IServiceCollection AddOpenApiConfiguration(this IServiceCollection services)
{
services.AddOpenApi(options =>
{
options.AddDocumentTransformer((document, _, _) =>
{
document.Info.Title = "Exchange Rate Updater API";
document.Info.Contact = new OpenApiContact
{
Name = "Amin Ch",
Email = "experimentalaminch@outlook.com"
};

return Task.CompletedTask;
});
});

return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace EchangeRateUpdater.Api.Configurations.Extensions;

using Serilog;

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddApiServices(this IServiceCollection services)
{
services.AddHealthChecks();

services
.AddExceptionHandler<GlobalExceptionHandler>()
.AddProblemDetails()
.AddSerilog()
.AddOpenApiConfiguration();

return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace ExchangeRateUpdater.Api.Controllers;

using Application.ExchangeRates.Dtos;
using Application.ExchangeRates.Query.GetExchangeRatesDaily;
using Mediator;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("exchange-rates")]
public class ExchangeRatesController (IMediator mediator) : Controller
{
[HttpGet]
[ProducesResponseType(typeof(List<ExchangeRateDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<ActionResult<List<ExchangeRateDto>>> GetExchangeRatesByDate(
[FromQuery] GetExchangesRatesByDateQuery query)
{
var result = await mediator.Send(query);
return Ok(result);
}
}
32 changes: 32 additions & 0 deletions jobs/Backend/Task/ExchangeRateUpdater.Api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src

# Copy csproj files to leverage Docker layer caching during restore
COPY ExchangeRateUpdater.Api/ExchangeRateUpdater.Api.csproj ExchangeRateUpdater.Api/
COPY ExchangeRateUpdater.Application/ExchangeRateUpdater.Application.csproj ExchangeRateUpdater.Application/
COPY ExchangeRateUpdater.Infrastructure/ExchangeRateUpdater.Infrastructure.csproj ExchangeRateUpdater.Infrastructure/
COPY ExchangeRateUpdater.Domain/ExchangeRateUpdater.Domain.csproj ExchangeRateUpdater.Domain/

RUN dotnet restore ExchangeRateUpdater.Api/ExchangeRateUpdater.Api.csproj

# Copy remaining sources
COPY . .

WORKDIR /src/ExchangeRateUpdater.Api
RUN dotnet build "ExchangeRateUpdater.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build

FROM build AS publish
ARG BUILD_CONFIGURATION=Release
WORKDIR /src/ExchangeRateUpdater.Api
RUN dotnet publish "ExchangeRateUpdater.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

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

# Start the published API DLL directly.
ENTRYPOINT ["dotnet", "ExchangeRateUpdater.Api.dll"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>EchangeRateUpdater.Api</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.8"/>
<PackageReference Include="Scalar.AspNetCore" Version="2.6.9"/>
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0"/>
</ItemGroup>

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

<ItemGroup>
<Content Update="appsettings.Development.json">
<DependentUpon>appsettings.json</DependentUpon>
</Content>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
namespace EchangeRateUpdater.Api;

using System.Text.Json;
using FluentValidation;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;

public class GlobalExceptionHandler(
IProblemDetailsService problemDetailsService,
ILogger<GlobalExceptionHandler> logger) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
logger.LogError(exception, "An unexpected error occurred while processing the request.");

var responseStatusCode = StatusCodes.Status500InternalServerError;
var problemDetails = new ProblemDetails
{
Type = "internal_server_error",
Title = "An unexpected error occurred",
Instance = httpContext.Request.Path
};

if (exception is ValidationException validationException)
{
responseStatusCode = StatusCodes.Status400BadRequest;
var errors = validationException.Errors
.GroupBy(x => x.PropertyName)
.ToDictionary(
g => JsonNamingPolicy.CamelCase.ConvertName(g.Key),
g => g.Select(x => x.ErrorMessage).ToArray()
);

problemDetails.Type = "validation_error";

if (errors.Count > 0)
{
problemDetails.Title = "One or more validation errors occurred";
problemDetails.Extensions.Add("errors", errors);
}
else
{
problemDetails.Title = validationException.Message;
}
}

httpContext.Response.StatusCode = responseStatusCode;
problemDetails.Status = responseStatusCode;
var context = new ProblemDetailsContext
{
HttpContext = httpContext,
Exception = exception,
ProblemDetails = problemDetails
};

await problemDetailsService.WriteAsync(context);
return true;
}
}
47 changes: 47 additions & 0 deletions jobs/Backend/Task/ExchangeRateUpdater.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using EchangeRateUpdater.Api.Configurations.Extensions;
using ExchangeRateUpdater.Application;
using ExchangeRateUpdater.Infrastructure;
using Serilog;

InitializeBootstrapLogger();

try
{
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddApiServices()
.AddApplicationServices()
.AddInfrastructure(builder.Configuration)
.AddControllers();

var app = builder.Build();

app.UseExceptionHandler();
app.UseHealthChecks("/health");

app.UseHttpsRedirection();
app.UseApiDocumentation();
app.MapControllers();

app.Run();
}
catch (Exception ex)
{
Log.Error(ex, "Unhandled exception");
}
finally
{
await Log.CloseAndFlushAsync();
}

return;


void InitializeBootstrapLogger()
{
var config = new LoggerConfiguration().WriteTo.Console();

Log.Logger = config.CreateBootstrapLogger();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "scalar",
"applicationUrl": "http://localhost:5087",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "scalar",
"applicationUrl": "https://localhost:7169;http://localhost:5087",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ExchangeRateApiClientConfig": {
"BaseUrl": "https://api.cnb.cz/cnbapi/exrates/"
},
"ApiDocumentation": {
"Enabled": true
},
"Redis": {
"Connection": "localhost:6379"
}
}
Loading