Refactor backend into structured endpoints, contracts, and middleware

This commit is contained in:
2026-01-28 17:05:39 +01:00
parent 3ec1808ad1
commit 9363b029df
4 changed files with 430 additions and 401 deletions

View File

@@ -0,0 +1,63 @@
using Microsoft.AspNetCore.Diagnostics;
namespace GameList.Infrastructure;
public static class PlayerIdentityExtensions
{
public const string PlayerCookieName = "player";
public static IApplicationBuilder UsePlayerIdentity(this IApplicationBuilder app)
{
app.Use(async (ctx, next) =>
{
var cookieOptions = new CookieOptions
{
HttpOnly = true,
SameSite = SameSiteMode.Strict,
Secure = !app.ApplicationServices.GetRequiredService<IWebHostEnvironment>().IsDevelopment(),
IsEssential = true,
Expires = DateTimeOffset.UtcNow.AddYears(1)
};
Guid playerId;
if (!ctx.Request.Cookies.TryGetValue(PlayerCookieName, out var value) || !Guid.TryParse(value, out playerId))
{
playerId = Guid.NewGuid();
}
ctx.Response.Cookies.Append(PlayerCookieName, playerId.ToString(), cookieOptions);
ctx.Items[PlayerCookieName] = playerId;
await next();
});
return app;
}
public static IApplicationBuilder UseGlobalExceptionLogging(this IApplicationBuilder app)
{
app.UseExceptionHandler(handler =>
{
handler.Run(async context =>
{
var feature = context.Features.Get<IExceptionHandlerFeature>();
var logger = context.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger("GlobalException");
if (feature?.Error != null)
{
logger.LogError(feature.Error, "Unhandled exception");
}
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/json";
await context.Response.WriteAsJsonAsync(new { error = "Unexpected server error" });
});
});
return app;
}
public static IEndpointRouteBuilder MapHealthChecks(this IEndpointRouteBuilder endpoints)
{
endpoints.MapGet("/health", () => Results.Ok(new { status = "ok" }));
return endpoints;
}
}