using System.Text.Json; using System.Text.Json.Serialization; using ArcaneLibs; using ArcaneLibs.Extensions; using Microsoft.EntityFrameworkCore; using Spacebar.ConfigModel; using Spacebar.ConfigModel.Extensions; using Spacebar.Interop.Authentication; using Spacebar.Interop.Authentication.AspNetCore; using Spacebar.Models.Db.Contexts; using Spacebar.Models.Generic.Constants; using Spacebar.UApi.Services; var builder = WebApplication.CreateBuilder(args); if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("APPSETTINGS_PATH"))) { Console.WriteLine("Loading appsettings from path: " + Environment.GetEnvironmentVariable("APPSETTINGS_PATH")); builder.Configuration.AddJsonFile(Environment.GetEnvironmentVariable("APPSETTINGS_PATH")!); } // Add services to the container. builder.Services.AddControllers(options => { options.MaxValidationDepth = null; // options.MaxIAsyncEnumerableBufferLimit = 1; }).AddJsonOptions(options => { options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; options.JsonSerializerOptions.WriteIndented = true; options.JsonSerializerOptions.MaxDepth = 100; // options.JsonSerializerOptions.DefaultBufferSize = ; }).AddMvcOptions(o => { o.SuppressOutputFormatterBuffering = true; }); // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); builder.Services.AddDbContextPool(options => { options .UseNpgsql(builder.Configuration.GetConnectionString("Spacebar")) .EnableDetailedErrors(); }); if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CONFIG_PATH"))) builder.Services.AddSingleton(sp => { var cfgPath = Environment.GetEnvironmentVariable("CONFIG_PATH"); sp.GetService>().LogInformation("Using config from path: {path}", cfgPath); return JsonSerializer.Deserialize(File.ReadAllText(cfgPath)); }); else builder.Services.AddSingleton(sp => { sp.GetService>().LogInformation("Using config from database..."); var db = sp.GetService(); var config = db.Configs .OrderBy(x => x.Key) .ToDictionary(x => x.Key, x => x.Value); var readConfig = config.ToNestedJsonObject(); return readConfig.Deserialize(); }); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } app.UseAuthorization(); app.MapControllers(); StreamingHttpClient.LogRequests = false; app.Use((context, next) => { context.Response.Headers["Access-Control-Allow-Origin"] = string.IsNullOrWhiteSpace(context.Request.Headers.Origin) ? "*" : context.Request.Headers.Origin; context.Response.Headers["Access-Control-Allow-Methods"] = string.IsNullOrWhiteSpace(context.Request.Headers.AccessControlRequestMethod) ? "GET, POST, PUT, DELETE, OPTIONS" : context.Request.Headers.AccessControlRequestMethod.ToString(); context.Response.Headers["Access-Control-Allow-Headers"] = string.IsNullOrWhiteSpace(context.Request.Headers.AccessControlRequestHeaders) ? "*, Authorization" : context.Request.Headers.AccessControlRequestHeaders.ToString(); context.Response.Headers.AccessControlAllowCredentials = "true"; if (context.Request.Method == "OPTIONS") { context.Response.StatusCode = 200; return Task.CompletedTask; } return next(); }); // add some special sauce app.Map("/", async context => { var client = new StreamingHttpClient(); var cfg = context.RequestServices.GetService(); var requestMessage = new HttpRequestMessage(new HttpMethod(context.Request.Method), cfg.FallbackApiEndpoint); foreach (var header in context.Request.Headers) if (header.Key is not ("Accept-Encoding" or "Host")) requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); var responseMessage = await client.SendUnhandledAsync(requestMessage, CancellationToken.None); context.Response.StatusCode = (int)responseMessage.StatusCode; foreach (var header in responseMessage.Headers) context.Response.Headers[header.Key] = header.Value.ToArray(); foreach (var header in responseMessage.Content.Headers) context.Response.Headers[header.Key] = header.Value.ToArray(); context.Response.Headers["X-SB-UApi-Status"] = "MISSING"; // await responseMessage.Content.CopyToAsync(context.Response.Body); var txt = await responseMessage.Content.ReadAsStringAsync(); txt = txt.Replace("your very own Spacebar instance", "your very own Spacebar instance with μAPI"); var data = txt.AsBytes().ToArray(); context.Response.Headers.ContentLength = data.Length; await context.Response.Body.WriteAsync(data); }); // fallback to proxy in case we dont have a specific endpoint... // TODO config app.MapFallback("{*_}", async context => { var client = new StreamingHttpClient(); var cfg = context.RequestServices.GetService(); var requestMessage = new HttpRequestMessage( new HttpMethod(context.Request.Method), cfg.FallbackApiEndpoint + context.Request.Path + context.Request.QueryString ) { Content = new StreamContent(context.Request.Body) }; Console.WriteLine(requestMessage.RequestUri); foreach (var header in context.Request.Headers) if (header.Key is not ("Accept-Encoding" or "Host")) requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); var responseMessage = await client.SendUnhandledAsync(requestMessage, CancellationToken.None); context.Response.StatusCode = (int)responseMessage.StatusCode; foreach (var header in responseMessage.Headers) context.Response.Headers[header.Key] = header.Value.ToArray(); foreach (var header in responseMessage.Content.Headers) context.Response.Headers[header.Key] = header.Value.ToArray(); await responseMessage.Content.CopyToAsync(context.Response.Body); }); app.Run();