Files
GameList/Program.cs

152 lines
5.0 KiB
C#

using GameList.Data;
using GameList.Endpoints;
using GameList.Infrastructure;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System.Text.Json.Serialization;
var builder = WebApplication.CreateBuilder(args);
var dataDirectory = Path.Combine(builder.Environment.ContentRootPath, "App_Data");
Directory.CreateDirectory(dataDirectory);
var dataProtectionDirectory = Path.Combine(dataDirectory, "keys");
Directory.CreateDirectory(dataProtectionDirectory);
var configuredConnection = builder.Configuration.GetConnectionString("Default");
var dbPath = Path.Combine(dataDirectory, "gamelist.db");
var connectionBuilder = new SqliteConnectionStringBuilder(string.IsNullOrWhiteSpace(configuredConnection)
? $"Data Source={dbPath}"
: configuredConnection);
if (connectionBuilder.DataSource.Contains("App_Data", StringComparison.OrdinalIgnoreCase))
{
var fileName = Path.GetFileName(connectionBuilder.DataSource);
connectionBuilder.DataSource = Path.Combine(dataDirectory, fileName);
}
else if (!Path.IsPathRooted(connectionBuilder.DataSource))
{
connectionBuilder.DataSource = Path.GetFullPath(connectionBuilder.DataSource, dataDirectory);
}
var connectionString = connectionBuilder.ToString();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(connectionString));
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
builder.Services.AddHttpClient();
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(dataProtectionDirectory));
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = PlayerIdentityExtensions.PlayerCookieName;
options.Cookie.HttpOnly = true;
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = builder.Environment.IsDevelopment()
? CookieSecurePolicy.SameAsRequest
: CookieSecurePolicy.Always;
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = ctx =>
{
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
},
OnRedirectToAccessDenied = ctx =>
{
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
}
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(PlayerIdentityExtensions.AdminPolicy, policy =>
policy.RequireClaim(PlayerIdentityExtensions.AdminClaim, "true"));
});
var app = builder.Build();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost
});
var basePath = builder.Configuration["BasePath"];
if (!string.IsNullOrWhiteSpace(basePath))
{
app.UsePathBase(basePath);
UpdateIndexMetaBase(app.Environment, basePath);
}
app.UseGlobalExceptionLogging();
app.UseAuthentication();
app.UseMiddleware<EnsurePlayerExistsMiddleware>();
app.UseAuthorization();
// Ensure database and migrations are applied on startup
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Database.Migrate();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.MapHealthChecks();
app.MapAuthEndpoints();
app.MapStateEndpoints();
app.MapSuggestEndpoints();
app.MapVoteEndpoints();
app.MapResultsEndpoints();
app.MapAdminEndpoints();
app.Run();
static void UpdateIndexMetaBase(IWebHostEnvironment env, string basePath)
{
try
{
var indexPath = Path.Combine(env.WebRootPath, "index.html");
if (!File.Exists(indexPath)) return;
var text = File.ReadAllText(indexPath);
var marker = "name=\"app-base\"";
var contentKey = "content=\"";
var markerIndex = text.IndexOf(marker, StringComparison.OrdinalIgnoreCase);
if (markerIndex < 0) return;
var contentIndex = text.IndexOf(contentKey, markerIndex, StringComparison.OrdinalIgnoreCase);
if (contentIndex < 0) return;
var valueStart = contentIndex + contentKey.Length;
var valueEnd = text.IndexOf('"', valueStart);
if (valueEnd < 0) return;
var current = text[valueStart..valueEnd];
var normalized = basePath.EndsWith('/') ? basePath.TrimEnd('/') : basePath;
if (current == normalized) return;
var updated = text[..valueStart] + normalized + text[valueEnd..];
File.WriteAllText(indexPath, updated);
}
catch
{
// If we can't rewrite, continue; frontend can still be set manually.
}
}
public partial class Program { }