using IntelligenceApi.Engines; using IntelligenceApi.Engines.Franchisee; using IntelligenceApi.Engines.Franchisor; using IntelligenceApi.Engines.General; var builder = WebApplication.CreateBuilder(args); // -------------------- // Container-friendly HTTP binding // -------------------- var port = Environment.GetEnvironmentVariable("PORT") ?? "8081"; builder.WebHost.UseUrls($"http://0.0.0.0:{port}"); // -------------------- // Services // -------------------- builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddHttpClient(); // ── Engines ── // General is a singleton — stateless, no DB dependency, safe to share. // Franchisee and Franchisor delegate to General; register as singletons too. // When real data services are added, switch to Scoped. builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); // -------------------- // Security: internal-only access // Simple shared key check — requests must include X-Internal-Key header // matching the INTELLIGENCE_INTERNAL_KEY environment variable. // The Gateway sets this key; the container is not publicly exposed. // -------------------- var internalKey = builder.Configuration["INTELLIGENCE_INTERNAL_KEY"] ?? Environment.GetEnvironmentVariable("INTELLIGENCE_INTERNAL_KEY"); var app = builder.Build(); // Health check (no auth — used by ACA liveness probe) app.MapGet("/health", () => Results.Ok(new { ok = true, service = "IntelligenceApi", timestamp = DateTimeOffset.UtcNow })); app.MapGet("/", () => Results.Ok(new { service = "IntelligenceApi", version = "1.0.0", status = "Spend distribution engine running" })); // Internal key middleware — validates all /api/* requests app.Use(async (context, next) => { var path = context.Request.Path.Value ?? ""; // Pass health and root through unauthenticated if (path == "/" || path.StartsWith("/health", StringComparison.OrdinalIgnoreCase) || path.StartsWith("/swagger", StringComparison.OrdinalIgnoreCase)) { await next(); return; } // In development, skip key check if not configured if (string.IsNullOrWhiteSpace(internalKey)) { if (app.Environment.IsDevelopment()) { await next(); return; } context.Response.StatusCode = 503; await context.Response.WriteAsJsonAsync(new { ok = false, error = "Service not configured (missing INTELLIGENCE_INTERNAL_KEY)" }); return; } if (!context.Request.Headers.TryGetValue("X-Internal-Key", out var key) || key.FirstOrDefault() != internalKey) { context.Response.StatusCode = 401; await context.Response.WriteAsJsonAsync(new { ok = false, error = "Unauthorized" }); return; } await next(); }); app.UseAuthorization(); app.MapControllers(); Console.WriteLine($"[IntelligenceApi] Starting on port {port}"); Console.WriteLine($"[IntelligenceApi] Internal key configured: {!string.IsNullOrWhiteSpace(internalKey)}"); app.Run();