using System.IO.Compression; using Microsoft.AspNetCore.ResponseCompression; using TheLivingWorld.Api.Endpoints; using TheLivingWorld.Api.Generation; using TheLivingWorld.Api.Simulation; using TheLivingWorld.Api.Storage; using TheLivingWorld.Core.Contracts; using TheLivingWorld.Core.Export; using TheLivingWorld.Osm; const string WebClientCorsPolicy = "web-client"; var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); builder.Services.ConfigureHttpJsonOptions(options => MapJson.Apply(options.SerializerOptions)); builder.Services.Configure( builder.Configuration.GetSection(WorldStorageOptions.SectionName)); builder.Services.Configure( builder.Configuration.GetSection(SimulationOptions.SectionName)); // Relative paths in configuration are meant to sit next to the app, not next to whatever the working // directory happens to be when it is launched. builder.Services.PostConfigure(options => options.RootDirectory = RootPath(options.RootDirectory)); #pragma warning disable EXTEXP0001 // RemoveAllResilienceHandlers is still marked experimental. builder.Services.AddOsmImport(builder.Configuration) // Overpass answers in minutes, not seconds; the resilience pipeline that AddServiceDefaults installs on // every client would abandon the request long before that. OverpassClient retries across mirrors itself. .RemoveAllResilienceHandlers(); #pragma warning restore EXTEXP0001 builder.Services.PostConfigure(options => options.CacheDirectory = RootPath(options.CacheDirectory)); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(provider => provider.GetRequiredService()); builder.Services.AddSingleton(); // Geometry payloads are highly repetitive JSON and compress by roughly an order of magnitude. builder.Services.AddResponseCompression(options => { options.EnableForHttps = true; options.Providers.Add(); options.Providers.Add(); options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/json"]); }); builder.Services.Configure(o => o.Level = CompressionLevel.Fastest); builder.Services.Configure(o => o.Level = CompressionLevel.Fastest); // The Vite dev server proxies /api during `aspire run`, but the client can also be pointed at the API // directly, which crosses an origin. builder.Services.AddCors(options => options.AddPolicy(WebClientCorsPolicy, policy => policy .SetIsOriginAllowed(origin => new Uri(origin).IsLoopback) .AllowAnyHeader() .AllowAnyMethod())); var app = builder.Build(); app.UseResponseCompression(); app.UseCors(WebClientCorsPolicy); app.MapDefaultEndpoints(); app.MapWorldEndpoints(); app.Run(); string RootPath(string path) => Path.IsPathRooted(path) ? path : Path.GetFullPath(Path.Combine(builder.Environment.ContentRootPath, path));