106 lines
3.2 KiB
C#
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();
|