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

More features #6

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
update to dotnet 9 and fix the related errors 🚀
  • Loading branch information
maxstue committed Nov 26, 2024
commit bba7f0459368ef126619b5e358bf1daf657d6995
416 changes: 325 additions & 91 deletions apps/api/.editorconfig

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions apps/api/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!-- See https://aka.ms/dotnet/msbuild/customize for more details on customizing your build -->
<Project>
<PropertyGroup>
<!-- Configure the version for all projects -->
<!-- <Version>0.5.0</Version>-->

<TargetFramework>net9.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>true</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<!-- Code Style and Analysis settings -->
<PropertyGroup>
<!-- TODO Should be enabled -->
<!-- <WarningsAsErrors>Nullable</WarningsAsErrors>-->
<!-- <CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>-->
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
<RunAnalyzersDuringLiveAnalysis>true</RunAnalyzersDuringLiveAnalysis>
<AnalysisLevel>latest</AnalysisLevel>
<AnalysisMode>Recommended</AnalysisMode>
<NoWarn>$(NoWarn);1591;</NoWarn>
</PropertyGroup>

<ItemGroup Condition="'$(MSBuildProjectExtension)' != '.dcproj'">
<PackageReference Include="SonarAnalyzer.CSharp"/>
</ItemGroup>

</Project>
54 changes: 54 additions & 0 deletions apps/api/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<!-- For more info on central package management go to https://devblogs.microsoft.com/nuget/introducing-central-package-management/ -->
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<!-- OpenApi-->
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0"/>
<PackageVersion Include="Microsoft.Extensions.ApiDescription.Server" Version="9.0.0"/>
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.1.0"/>
<PackageVersion Include="AspNetCore.HealthChecks.NpgSql" Version="8.0.2"/>
<PackageVersion Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.1"/>
<!-- Auth -->
<PackageVersion Include="Microsoft.Identity.Web" Version="3.4.0"/>
<PackageVersion Include="Microsoft.Identity.Web.DownstreamApi" Version="3.4.0"/>
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0"/>
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.0"/>
<!-- Database -->
<PackageVersion Include="EFCore.NamingConventions" Version="8.0.3"/>
<PackageVersion Include="EntityFrameworkCore.Exceptions.PostgreSQL" Version="8.1.3"/>
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.1"/>
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<!-- Logging -->
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.3"/>
<PackageVersion Include="Serilog.Expressions" Version="5.0.0"/>
<!-- Telemetry -->
<PackageVersion Include="Sentry.AspNetCore" Version="4.13.0"/>
<PackageVersion Include="Sentry.Serilog" Version="4.13.0"/>
<!-- Linting -->
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.3.0.106239">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<!-- Other -->
<PackageVersion Include="FluentValidation.AspNetCore" Version="11.3.0"/>
<PackageVersion Include="Humanizer.Core" Version="2.14.1"/>
<PackageVersion Include="Humanizer.Core.de" Version="2.14.1"/>
<PackageVersion Include="MinVer" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="NetEscapades.AspNetCore.SecurityHeaders" Version="0.24.0"/>
<PackageVersion Include="NetEscapades.EnumGenerators" Version="1.0.0-beta11" PrivateAssets="all" ExcludeAssets="runtime"/>
</ItemGroup>
</Project>
51 changes: 36 additions & 15 deletions apps/api/Kijk.Api/Common/Extensions/ApplicationExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,52 @@
using HealthChecks.UI.Client;

using Kijk.Api.Common.Models;
using Kijk.Api.Endpoints;
using Kijk.Api.Persistence;

using Microsoft.AspNetCore.Diagnostics.HealthChecks;

using Swashbuckle.AspNetCore.SwaggerUI;

namespace Kijk.Api.Common.Extensions;

public static class ApplicationExtensions
{
public static IApplicationBuilder UseCustomOpenApi(this IApplicationBuilder applicationBuilder)
public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder applicationBuilder)
{
applicationBuilder.UseSwagger(
c =>
applicationBuilder.UseSerilogRequestLogging(
options => options.GetLevel = new Func<HttpContext, double, Exception?, LogEventLevel>((ctx, _, ex) =>
{
c.RouteTemplate = "api/swagger/{documentName}/swagger.json";
});
if (ex == null && ctx.Response.StatusCode <= 499)
{
return LogEventLevel.Information;
}

if (ctx.Request.Path.StartsWithSegments("/api/health") && ex is OperationCanceledException)
{
// If the incoming HTTP request for a healthcheck is aborted, don't log the resultant OperationCanceledException
// as an error. beware that the ASP.NET DefaultHealthCheckService ensures that if the exception occurs
// within the healthcheck implementation (and the request wasn't aborted) a failed healthcheck is logged
// see https://github.com/dotnet/aspnetcore/blob/ce9e1ae5500c3f0c4b9bd682fd464b3493e48e61/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs#L121
return LogEventLevel.Information;
}

return LogEventLevel.Error;
}));

return applicationBuilder;
}

public static IApplicationBuilder UseOpenApi(this IApplicationBuilder applicationBuilder)
{
applicationBuilder.UseSwaggerUI(
c =>
{
c.SwaggerEndpoint("/api/openapi.json", "Kijk Api v1.0");
c.DefaultModelsExpandDepth(0);
c.DefaultModelExpandDepth(0);
c.SwaggerEndpoint("v1/swagger.json", "Kijk Api v1.00");
c.DocExpansion(DocExpansion.None);
c.DocumentTitle = "SwaggerUI - Kijk Api";
c.RoutePrefix = "api/swagger";
});
return applicationBuilder;
Expand All @@ -41,22 +67,17 @@ public static IApplicationBuilder ApplyMigrations(this IApplicationBuilder appli
using var scope = applicationBuilder.ApplicationServices.CreateScope();
using var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

dbContext.Database.MigrateAsync();
// dbContext.Database.MigrateAsync();
Log.ForContext(typeof(AppDbInitializer)).Information("Database migrations applied");

return applicationBuilder;
}

public static IApplicationBuilder MapApiEndpoints(this IApplicationBuilder applicationBuilder)
public static WebApplication MapApiEndpoints(this WebApplication app)
{
var app = (WebApplication)applicationBuilder;
app.Map("/", () => Results.Redirect("api/swagger"));

var apiGroup = app.MapGroup("/api")
.RequireAuthorization(AppConstants.Policies.All)
.WithOpenApi();

apiGroup.RequirePerUserRateLimit();
.RequirePerUserRateLimit();

apiGroup.MapTransactionsEndpoints();
apiGroup.MapUsersApi();
Expand All @@ -72,4 +93,4 @@ public static IApplicationBuilder MapHealthCheck(this IApplicationBuilder applic

return applicationBuilder;
}
}
}
53 changes: 53 additions & 0 deletions apps/api/Kijk.Api/Common/Extensions/BearerAuthSchemeTransformer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using Kijk.Api.Common.Options;

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Models;

namespace Kijk.Api.Common.Extensions;

/// <summary>
/// Schema transformer for the OpenApi document to add the Oauth2 (Bearer) authentication.
/// </summary>
/// <param name="authenticationSchemeProvider"></param>
/// <param name="configuration"></param>
public sealed class BearerAuthSchemeTransformer(IAuthenticationSchemeProvider authenticationSchemeProvider, IConfiguration configuration)
: IOpenApiDocumentTransformer
{
public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
{
var appSettings = configuration.GetSection(AuthOptions.SectionName).Get<AuthOptions>();

if (appSettings is null)
{
throw new Exception($"Keine AuthOptions gefunden, {appSettings}");
}

var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer"))
{
var requirements = new Dictionary<string, OpenApiSecurityScheme>
{
["Bearer"] = new()
{
Type = SecuritySchemeType.Http,
Scheme = "bearer",
In = ParameterLocation.Header,
BearerFormat = "Json Web Token",
Description = "JWT Authorization header using the Bearer scheme."
}
};
document.Components ??= new OpenApiComponents();
document.Components.SecuritySchemes = requirements;
// Apply it as a requirement for all operations
foreach (var operation in document.Paths.Values.SelectMany(path => path.Operations))
{
operation.Value.Security.Add(new OpenApiSecurityRequirement
{
[new OpenApiSecurityScheme { Reference = new OpenApiReference { Id = "Bearer", Type = ReferenceType.SecurityScheme } }] =
Array.Empty<string>()
});
}
}
}
}
28 changes: 11 additions & 17 deletions apps/api/Kijk.Api/Common/Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,15 @@

public static class EnumerableExtensions
{
public static IQueryable<T> If<T>(this IQueryable<T> query, bool should, params Func<IQueryable<T>, IQueryable<T>>[] transforms)
{
return should
? transforms.Aggregate(
query,
(current, transform) => transform.Invoke(current))
: query;
}
public static IQueryable<T> If<T>(this IQueryable<T> query, bool should, params Func<IQueryable<T>, IQueryable<T>>[] transforms) => should
? transforms.Aggregate(
query,
(current, transform) => transform.Invoke(current))
: query;

public static IEnumerable<T> If<T>(this IEnumerable<T> query, bool should, params Func<IEnumerable<T>, IEnumerable<T>>[] transforms)
{
return should
? transforms.Aggregate(
query,
(current, transform) => transform.Invoke(current))
: query;
}
}
public static IEnumerable<T> If<T>(this IEnumerable<T> query, bool should, params Func<IEnumerable<T>, IEnumerable<T>>[] transforms) => should
? transforms.Aggregate(
query,
(current, transform) => transform.Invoke(current))
: query;
}
16 changes: 5 additions & 11 deletions apps/api/Kijk.Api/Common/Extensions/OptionsExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,10 @@ namespace Kijk.Api.Common.Extensions;
public static class OptionsExtensions
{
public static IServiceCollection ConfigureOptions<TOptions>(this IServiceCollection services, IConfiguration configuration)
where TOptions : class, IConfigOptions
{
return services.Configure<TOptions>(configuration.GetSection(TOptions.SectionName));
}
where TOptions : class, IConfigOptions => services.Configure<TOptions>(configuration.GetSection(TOptions.SectionName));

public static TOptions? GetConfigurationSection<TOptions>(this IHostApplicationBuilder builder)
where TOptions : class, IConfigOptions
{
return builder.Configuration
.GetSection(TOptions.SectionName)
.Get<TOptions>();
}
}
where TOptions : class, IConfigOptions => builder.Configuration
.GetSection(TOptions.SectionName)
.Get<TOptions>();
}
45 changes: 20 additions & 25 deletions apps/api/Kijk.Api/Common/Extensions/RateLimitExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,32 @@ namespace Kijk.Api.Common.Extensions;

public static class RateLimitExtensions
{
public static IServiceCollection AddRateLimitPolicy(this IServiceCollection services) => services.AddRateLimiter(
options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

public static IServiceCollection AddRateLimitPolicy(this IServiceCollection services)
{
return services.AddRateLimiter(
options =>
options.AddPolicy(AppConstants.Policies.RateLimit, context =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
var username = context.User.FindFirstValue(ClaimTypes.NameIdentifier)!;

options.AddPolicy(
AppConstants.Policies.RateLimit,
context =>
// 100 Requests per 10sec per user
return RateLimitPartition.GetTokenBucketLimiter(
username,
_ => new TokenBucketRateLimiterOptions
{
var username = context.User.FindFirstValue(ClaimTypes.NameIdentifier)!;

// 100 Requests per 10sec per user
return RateLimitPartition.GetTokenBucketLimiter(
username,
_ => new TokenBucketRateLimiterOptions
{
ReplenishmentPeriod = TimeSpan.FromSeconds(10),
AutoReplenishment = true,
TokenLimit = 100,
TokensPerPeriod = 100,
QueueLimit = 100
});
ReplenishmentPeriod = TimeSpan.FromSeconds(10),
AutoReplenishment = true,
TokenLimit = 100,
TokensPerPeriod = 100,
QueueLimit = 100
});
});
}
});

public static IEndpointConventionBuilder RequirePerUserRateLimit(this IEndpointConventionBuilder builder)
public static RouteGroupBuilder RequirePerUserRateLimit(this RouteGroupBuilder builder)
{
return builder.RequireRateLimiting(AppConstants.Policies.RateLimit);
builder.RequireRateLimiting(AppConstants.Policies.RateLimit);
return builder;
}
}
}
11 changes: 5 additions & 6 deletions apps/api/Kijk.Api/Common/Extensions/RouteExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net;

using Kijk.Api.Common.Filters;

namespace Kijk.Api.Common.Extensions;
Expand All @@ -11,9 +12,7 @@ public static class RouteExtensions
/// <param name="builder"></param>
/// <typeparam name="TRequest"></typeparam>
/// <returns></returns>
public static RouteHandlerBuilder WithRequestValidation<TRequest>(this RouteHandlerBuilder builder) where TRequest : class
{
return builder.AddEndpointFilter<ValidationFilter<TRequest>>()
.Produces((int)HttpStatusCode.BadRequest);
}
}
public static RouteHandlerBuilder WithRequestValidation<TRequest>(this RouteHandlerBuilder builder) where TRequest : class => builder
.AddEndpointFilter<ValidationFilter<TRequest>>()
.Produces((int)HttpStatusCode.BadRequest);
}
Loading