Files
2026-03-14 13:50:09 -07:00

106 lines
3.2 KiB
C#

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<GeneralEngine>();
builder.Services.AddSingleton<FranchiseeEngine>();
builder.Services.AddSingleton<FranchisorEngine>();
builder.Services.AddSingleton<EngineRouter>();
builder.Services.AddSingleton<DemographicsAnalyzer>();
// --------------------
// 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();