Remove startup migration and runtime frontend rewrites

This commit is contained in:
2026-02-08 21:46:26 +01:00
parent 2d2201d0a2
commit acffbc199d
6 changed files with 12 additions and 90 deletions

View File

@@ -9,7 +9,6 @@ using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Configuration;
using System.Text.Json;
using System.Net.Http.Json;
@@ -28,34 +27,10 @@ public class HelperTests
}
[Fact]
public void UpdateIndexMetaBase_rewrites_content_value()
public void Program_does_not_include_runtime_index_rewrite_hook()
{
var webRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(webRoot);
var index = Path.Combine(webRoot, "index.html");
File.WriteAllText(index, "<meta name=\"app-base\" content=\"\">");
var env = new FakeEnv { WebRootPath = webRoot };
var method = typeof(Program).GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).First(m => m.Name.Contains("UpdateIndexMetaBase"));
method.Invoke(null, [env, "/pick"]);
var text = File.ReadAllText(index);
Assert.Contains("content=\"/pick\"", text);
}
[Fact]
public void UpdateIndexMetaBase_no_marker_no_change()
{
var webRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(webRoot);
var index = Path.Combine(webRoot, "index.html");
File.WriteAllText(index, "<html></html>");
var env = new FakeEnv { WebRootPath = webRoot };
var method = typeof(Program).GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).First(m => m.Name.Contains("UpdateIndexMetaBase"));
method.Invoke(null, [env, "/pick"]);
Assert.Equal("<html></html>", File.ReadAllText(index));
var hasRewriteMethod = typeof(Program).GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).Any(m => m.Name.Contains("UpdateIndexMetaBase", StringComparison.Ordinal));
Assert.False(hasRewriteMethod);
}
[Fact]
@@ -349,16 +324,6 @@ public class HelperTests
Assert.DoesNotContain("data-name=\"${v.name}\"", adminJs, StringComparison.Ordinal);
}
private class FakeEnv : IWebHostEnvironment
{
public string ApplicationName { get; set; } = "";
public IFileProvider WebRootFileProvider { get; set; } = null!;
public string WebRootPath { get; set; } = "";
public string EnvironmentName { get; set; } = "";
public string ContentRootPath { get; set; } = "";
public IFileProvider ContentRootFileProvider { get; set; } = null!;
}
private static ForwardedHeadersOptions BuildForwardedHeadersOptionsForTest(IConfiguration config)
{
var method = typeof(Program).GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).First(m => m.Name.Contains("BuildForwardedHeadersOptions"));

View File

@@ -26,7 +26,7 @@ internal class TestWebApplicationFactory : WebApplicationFactory<Program>
services.Remove(descriptor);
}
_connection = new SqliteConnection("Data Source=:memory:;Cache=Shared");
_connection = new SqliteConnection($"Data Source=file:tests-{Guid.NewGuid():N}?mode=memory&cache=shared");
_connection.Open();
services.AddDbContext<AppDbContext>(options => { options.UseSqlite(_connection); });
@@ -44,7 +44,6 @@ internal class TestWebApplicationFactory : WebApplicationFactory<Program>
using var scope = host.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Database.EnsureCreated();
db.Database.Migrate();
return host;

1
IIS.md
View File

@@ -8,6 +8,7 @@
## Publish
- From repo root: `dotnet publish -c Release -o publish`
- Before first start (and after every new migration): run `dotnet ef database update` from repo root against the target environment.
- Copy `publish/` contents to site directory (keep `App_Data` writable by the app pool user).
- Set environment variables in web.config or IIS config:
- `ASPNETCORE_ENVIRONMENT=Production`

View File

@@ -146,7 +146,6 @@ var basePath = builder.Configuration["BasePath"];
if (!string.IsNullOrWhiteSpace(basePath))
{
app.UsePathBase(basePath);
UpdateIndexMetaBase(app.Environment, basePath);
}
app.UseGlobalExceptionLogging();
@@ -154,13 +153,6 @@ 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();
@@ -274,42 +266,4 @@ static Task WriteUnauthorizedChallengeAsync(HttpContext context)
return context.Response.WriteAsJsonAsync(problem);
}
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;

View File

@@ -6,11 +6,13 @@ Pick'n'Play is a .NET 10 ASP.NET Core Minimal API app with a static HTML/CSS/JS
1. Restore and build:
`dotnet build GameList.sln`
2. Run tests:
2. Apply DB migrations explicitly:
`dotnet ef database update`
3. Run tests:
`dotnet test GameList.Tests/GameList.Tests.csproj`
3. Run locally:
4. Run locally:
`dotnet run --project GameList.csproj`
4. Open:
5. Open:
`http://localhost:5000` (or the URL shown by `dotnet run`)
## Frontend Tooling
@@ -28,6 +30,7 @@ Pick'n'Play is a .NET 10 ASP.NET Core Minimal API app with a static HTML/CSS/JS
- Core invariants are DB-enforced: single owner account and non-joker suggestion cap.
- Gameplay phases: `Suggest`, `Vote`, `Results`.
- Storage: SQLite database under `App_Data/gamelist.db`.
- Migrations are deployment-time operations (`dotnet ef database update`); app startup does not auto-migrate.
- Security defaults: rate-limited auth/admin routes, baseline browser security headers, production HTTPS+HSTS enforcement.
## Module Ownership

View File

@@ -87,7 +87,7 @@ stateDiagram-v2
- EndpointHelpers.IsValidImageUrl/IsValidHttpUrl: accepts empty, http/https; rejects others/invalid ext.
- IsReachableImageAsync: with mocked Http responses covers head success, get fallback, redirect rejection, size guard, and private/reserved host range detection (IPv4/IPv6).
- BuildLinkRoots/LinkedIdsFor/FindRootId: cover disjoint groups, chains, cycles guard (visited set), non-existent ids.
- UpdateIndexMetaBase (Program.cs): rewrites app-base meta when BasePath set; no change when matching/marker missing; safe exceptions swallowed.
- Program startup avoids runtime frontend file rewrites; BasePath remains purely configuration/deploy managed.
- Global exception handler returns 500 with JSON body and logs error.
- /health returns {status:"ok"}.
- Security middleware tests validate response headers and rate-limiting behavior on auth/admin routes.