216 lines
7.7 KiB
C#
216 lines
7.7 KiB
C#
using Azure.Storage.Blobs;
|
|
using Gateway.Data;
|
|
using Gateway.Models;
|
|
using Gateway.ProviderClients;
|
|
using Gateway.Security;
|
|
using Gateway.Services;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// --------------------
|
|
// Container-friendly HTTP binding
|
|
// --------------------
|
|
var port = Environment.GetEnvironmentVariable("PORT") ?? "8080";
|
|
builder.WebHost.UseUrls($"http://0.0.0.0:{port}");
|
|
|
|
|
|
// --------------------
|
|
// CORS — allowed origins from env var, comma-separated
|
|
// --------------------
|
|
var allowedOrigins = (builder.Configuration["CORS__AllowedOrigins"] ?? "")
|
|
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
|
|
|
builder.Services.AddCors(options =>
|
|
{
|
|
options.AddDefaultPolicy(policy =>
|
|
{
|
|
if (allowedOrigins.Length > 0)
|
|
policy.WithOrigins(allowedOrigins)
|
|
.AllowAnyHeader()
|
|
.AllowAnyMethod();
|
|
else
|
|
policy.AllowAnyOrigin()
|
|
.AllowAnyHeader()
|
|
.AllowAnyMethod();
|
|
});
|
|
});
|
|
|
|
// --------------------
|
|
// Services
|
|
// --------------------
|
|
builder.Services.AddControllers();
|
|
builder.Services.AddEndpointsApiExplorer();
|
|
builder.Services.AddSwaggerGen();
|
|
|
|
// Data & business services
|
|
builder.Services.AddScoped<SqlService>();
|
|
|
|
// Channel configuration (loaded from DB at startup, not appsettings)
|
|
builder.Services.AddSingleton<ChannelConfigService>();
|
|
|
|
// For consumers injecting MultiChannelConfig directly (e.g. ExecutionService)
|
|
builder.Services.AddScoped<MultiChannelConfig>(sp =>
|
|
sp.GetRequiredService<ChannelConfigService>().Current);
|
|
|
|
// For consumers injecting IOptions<MultiChannelConfig> (e.g. InitiativeController, InitiativeLaunchService)
|
|
builder.Services.AddSingleton<Microsoft.Extensions.Options.IOptions<MultiChannelConfig>>(sp =>
|
|
Microsoft.Extensions.Options.Options.Create(
|
|
sp.GetRequiredService<ChannelConfigService>().Current));
|
|
|
|
// Authentication context (scoped - one per request)
|
|
builder.Services.AddScoped<ClientContext>();
|
|
|
|
// Provider clients
|
|
builder.Services.AddHttpClient<GoogleProviderClient>(client =>
|
|
{
|
|
var baseUrl = builder.Configuration["Provider:Google:BaseUrl"]
|
|
?? Environment.GetEnvironmentVariable("GOOGLE_PROVIDER_URL")
|
|
?? "";
|
|
if (!string.IsNullOrWhiteSpace(baseUrl))
|
|
client.BaseAddress = new Uri(baseUrl.EndsWith("/") ? baseUrl : baseUrl + "/");
|
|
});
|
|
|
|
// HTTP client factory for ExecutionService
|
|
builder.Services.AddHttpClient();
|
|
|
|
// --------------------
|
|
// Blob Storage (for Creative images)
|
|
// --------------------
|
|
var blobConnectionString = builder.Configuration["BlobStorage:ConnectionString"]
|
|
?? Environment.GetEnvironmentVariable("BLOB_STORAGE_CONNECTION_STRING");
|
|
|
|
if (!string.IsNullOrEmpty(blobConnectionString))
|
|
{
|
|
builder.Services.AddSingleton(new BlobServiceClient(blobConnectionString));
|
|
Console.WriteLine("[Gateway] Blob storage configured");
|
|
}
|
|
else
|
|
{
|
|
// Register null so DI can resolve ImageStorageService
|
|
builder.Services.AddSingleton<BlobServiceClient>(sp => null!);
|
|
Console.WriteLine("[Gateway] Blob storage not configured - Creative images will use source URLs");
|
|
}
|
|
|
|
// ImageStorageService (works with or without blob storage configured)
|
|
builder.Services.AddScoped<ImageStorageService>();
|
|
|
|
// ExecutionService (depends on ImageStorageService)
|
|
builder.Services.AddScoped<ExecutionService>();
|
|
|
|
// Metric sync orchestration (pulls from providers, writes to DB, triggers evaluation)
|
|
builder.Services.AddScoped<MetricSyncService>();
|
|
|
|
// Initiative launch orchestration service
|
|
builder.Services.AddScoped<InitiativeLaunchService>();
|
|
|
|
// Authorization guard (ownership, roles, status transitions)
|
|
builder.Services.AddHttpContextAccessor();
|
|
builder.Services.AddScoped<AuthorizationGuard>();
|
|
|
|
// Provider status normalization
|
|
builder.Services.AddSingleton<ProviderStatusNormalizer>();
|
|
|
|
// Forecast service for channel performance estimates (local fallback)
|
|
builder.Services.AddSingleton<ForecastService>();
|
|
|
|
// IntelligenceApi client — routes forecast requests to the category-aware engine container
|
|
// Falls back to ForecastService if INTELLIGENCE_API_URL is not configured
|
|
builder.Services.AddSingleton<IntelligenceApiClient>();
|
|
|
|
var app = builder.Build();
|
|
|
|
// ────────────────────────────────────────────────
|
|
// Load channel config from database (before serving requests)
|
|
// ────────────────────────────────────────────────
|
|
try
|
|
{
|
|
var channelConfigSvc = app.Services.GetRequiredService<ChannelConfigService>();
|
|
await channelConfigSvc.LoadAsync();
|
|
Console.WriteLine("[Gateway] Channel config loaded from database");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[Gateway] ⚠️ Channel config DB load failed — using defaults: {ex.Message}");
|
|
}
|
|
|
|
// ────────────────────────────────────────────────
|
|
// SECURITY: Startup environment checks
|
|
// ────────────────────────────────────────────────
|
|
var env = app.Environment;
|
|
var allowDevBypass = builder.Configuration.GetValue<bool>("Auth:AllowDevBypass");
|
|
|
|
if (allowDevBypass && !env.IsDevelopment())
|
|
{
|
|
Console.WriteLine("╔══════════════════════════════════════════════════════════╗");
|
|
Console.WriteLine("║ ⚠️ WARNING: Auth:AllowDevBypass=true in NON-DEV env! ║");
|
|
Console.WriteLine("║ This allows X-Dev-ClientId header to bypass auth. ║");
|
|
Console.WriteLine("║ Remove this setting in production! ║");
|
|
Console.WriteLine("╚══════════════════════════════════════════════════════════╝");
|
|
}
|
|
|
|
if (env.IsDevelopment())
|
|
{
|
|
Console.WriteLine("[Gateway] ⚠️ Development mode — dev bypass headers accepted");
|
|
}
|
|
|
|
// --------------------
|
|
// Middleware pipeline
|
|
// --------------------
|
|
|
|
// Swagger (enabled for all environments in containers)
|
|
app.UseSwagger();
|
|
app.UseSwaggerUI();
|
|
|
|
// Health check endpoint (before auth & logging)
|
|
app.MapGet("/health", (ChannelConfigService channelSvc, IConfiguration config) =>
|
|
{
|
|
var blobConfigured = !string.IsNullOrEmpty(config["BlobStorage:ConnectionString"]) ||
|
|
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("BLOB_STORAGE_CONNECTION_STRING"));
|
|
|
|
var mcConfig = channelSvc.Current;
|
|
|
|
return Results.Ok(new
|
|
{
|
|
ok = true,
|
|
service = "Gateway",
|
|
timestamp = DateTimeOffset.UtcNow,
|
|
config = new
|
|
{
|
|
blobStorageConfigured = blobConfigured,
|
|
blobContainer = config["BlobStorage:ContainerName"] ?? "creative-images",
|
|
enabledChannels = mcConfig.EnabledChannels.Select(c => new
|
|
{
|
|
c.ChannelType,
|
|
c.DisplayName,
|
|
c.IsStub
|
|
})
|
|
}
|
|
});
|
|
});
|
|
|
|
// Root endpoint
|
|
app.MapGet("/", () => Results.Ok(new
|
|
{
|
|
service = "Gateway API",
|
|
version = "1.0.0",
|
|
status = "Application Gateway running"
|
|
}));
|
|
|
|
// CORS — must be before auth middleware
|
|
app.UseCors();
|
|
|
|
// Access logging middleware (captures all requests)
|
|
// Placed BEFORE auth so we log even failed auth attempts
|
|
app.UseAccessLogging();
|
|
|
|
// Client authentication middleware (multi-provider)
|
|
// - Validates JWTs from Microsoft, Google, etc.
|
|
// - Accepts X-Dev-ClientId header (development)
|
|
app.UseMiddleware<MultiProviderAuthMiddleware>();
|
|
|
|
// Standard middleware
|
|
app.UseAuthorization();
|
|
app.MapControllers();
|
|
|
|
app.Run();
|