Renders the markdown documents in Content/Socialism: a landing overview at
/socialism and chapters at /socialism/{slug}, with a sectioned sidebar,
on-page table of contents, prev/next paging and cross-document links.
Pages are noindex and nothing on the site links to them. Underscore-prefixed
files (founding draft, progress log) are never served, deployed or committed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
118 lines
4.7 KiB
C#
118 lines
4.7 KiB
C#
using JoshHeaps.Net.Hubs;
|
|
using JoshHeaps.Net.Services.Implementations;
|
|
using JoshHeaps.Net.Services.Interfaces;
|
|
using Microsoft.AspNetCore.StaticFiles;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// Add services to the container.
|
|
builder.Services.AddRazorPages();
|
|
var configuration = builder.Configuration;
|
|
|
|
builder.Services.AddControllers();
|
|
|
|
builder.Services.AddSignalR();
|
|
|
|
builder.Services.AddHttpClient("BlogApi", client =>
|
|
{
|
|
var baseUrl = configuration["BlogApi:BaseUrl"] ?? "https://media.joshheaps.net";
|
|
client.BaseAddress = new Uri(baseUrl);
|
|
client.Timeout = TimeSpan.FromSeconds(10);
|
|
});
|
|
builder.Services.AddSingleton<IBlogService, BlogService>();
|
|
builder.Services.AddSingleton<ISocialismDocService, SocialismDocService>();
|
|
builder.Services.AddSingleton<IChessService, ChessService>();
|
|
builder.Services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
|
|
|
|
builder.Services.Configure<ChessEngineOptions>(configuration.GetSection(ChessEngineOptions.SectionName));
|
|
builder.Services.AddSingleton<ILearnedWeightsStore, LearnedWeightsStore>();
|
|
builder.Services.AddSingleton<IChessEngineFactory, ChessEngineFactory>();
|
|
builder.Services.AddSingleton<IComputerMoveOrchestrator, ComputerMoveOrchestrator>();
|
|
builder.Services.AddSingleton<IGameStore, GameStore>();
|
|
builder.Services.AddSingleton<ISelfPlayCoordinator, SelfPlayCoordinator>();
|
|
builder.Services.AddSingleton<AutoTrainingSettings>();
|
|
|
|
if (!builder.Environment.IsDevelopment())
|
|
{
|
|
builder.Services.AddHostedService<AutoIpUpdateService>();
|
|
|
|
// Continuously train the learned engine against Stockfish in the background. Toggle off
|
|
// via ChessEngine:AutoTrain (env ChessEngine__AutoTrain=false) without a redeploy.
|
|
if (configuration.GetValue($"{ChessEngineOptions.SectionName}:{nameof(ChessEngineOptions.AutoTrain)}", true))
|
|
builder.Services.AddHostedService<AutoTrainingService>();
|
|
}
|
|
|
|
var app = builder.Build();
|
|
|
|
// Configure the HTTP request pipeline.
|
|
if (!app.Environment.IsDevelopment())
|
|
{
|
|
app.UseExceptionHandler("/Error");
|
|
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
|
app.UseHsts();
|
|
}
|
|
|
|
app.UseHttpsRedirection();
|
|
|
|
// One spelling of the casino URLs, because the Hold'em tool's offline support depends
|
|
// on it and nothing warns you when it breaks.
|
|
//
|
|
// Razor Pages routing is case-insensitive, so /Casino/TexasHoldem and
|
|
// /casino/texasholdem both serve the dealer tool and look identical. A service worker's
|
|
// scope is not: the spec matches it as a plain string prefix, so a worker scoped to
|
|
// "/casino/" does not control a page at "/Casino/TexasHoldem". Measured, that meant the
|
|
// capital-C spelling registered the worker, activated it, logged nothing — and then left
|
|
// the cache completely empty, because the page was never controlled and the precache
|
|
// handshake it waited on could not resolve. A dealer who bookmarked that URL had a tool
|
|
// that looked loaded and had nothing stored for the night the signal went.
|
|
//
|
|
// So one spelling wins and the other is redirected to it, permanently, before anything
|
|
// else in the pipeline gets a look.
|
|
//
|
|
// Deliberately scoped to /casino rather than applied site-wide. Every file under
|
|
// wwwroot/casino and wwwroot/images/casino is already lowercase, so lowercasing a path
|
|
// here can never miss a real file on a case-sensitive filesystem — a guarantee that does
|
|
// not hold for the rest of the site, and one that /chessHub in particular would break.
|
|
app.Use(async (context, next) =>
|
|
{
|
|
var path = context.Request.Path.Value;
|
|
if (path is not null
|
|
&& path.StartsWith("/casino", StringComparison.OrdinalIgnoreCase)
|
|
&& path.Any(char.IsUpper))
|
|
{
|
|
context.Response.Redirect(path.ToLowerInvariant() + context.Request.QueryString, permanent: true);
|
|
return;
|
|
}
|
|
|
|
await next();
|
|
});
|
|
|
|
// .webmanifest isn't in the default MIME map, and an unknown extension isn't served
|
|
// with the wrong type — it isn't served at all. Without this the Hold'em tool's
|
|
// manifest 404s, and a 404 manifest is exactly the silent failure that stops a browser
|
|
// ever offering "Add to Home Screen": nothing is logged and no prompt appears.
|
|
var contentTypes = new FileExtensionContentTypeProvider();
|
|
contentTypes.Mappings[".webmanifest"] = "application/manifest+json";
|
|
|
|
app.UseStaticFiles(new StaticFileOptions
|
|
{
|
|
ContentTypeProvider = contentTypes,
|
|
OnPrepareResponse = ctx =>
|
|
{
|
|
ctx.Context.Response.Headers.Append("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
ctx.Context.Response.Headers.Append("Pragma", "no-cache");
|
|
ctx.Context.Response.Headers.Append("Expires", "0");
|
|
}
|
|
});
|
|
|
|
app.UseRouting();
|
|
|
|
app.UseAuthorization();
|
|
|
|
app.MapRazorPages();
|
|
|
|
app.MapControllers();
|
|
|
|
app.MapHub<ChessHub>("/chessHub");
|
|
|
|
app.Run(); |