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(); // Channel configuration (loaded from DB at startup, not appsettings) builder.Services.AddSingleton(); // For consumers injecting MultiChannelConfig directly (e.g. ExecutionService) builder.Services.AddScoped(sp => sp.GetRequiredService().Current); // For consumers injecting IOptions (e.g. InitiativeController, InitiativeLaunchService) builder.Services.AddSingleton>(sp => Microsoft.Extensions.Options.Options.Create( sp.GetRequiredService().Current)); // Authentication context (scoped - one per request) builder.Services.AddScoped(); // Provider clients builder.Services.AddHttpClient(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(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(); // ExecutionService (depends on ImageStorageService) builder.Services.AddScoped(); // Metric sync orchestration (pulls from providers, writes to DB, triggers evaluation) builder.Services.AddScoped(); // Initiative launch orchestration service builder.Services.AddScoped(); // Authorization guard (ownership, roles, status transitions) builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); // Provider status normalization builder.Services.AddSingleton(); // Forecast service for channel performance estimates (local fallback) builder.Services.AddSingleton(); // 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(); var app = builder.Build(); // ──────────────────────────────────────────────── // Load channel config from database (before serving requests) // ──────────────────────────────────────────────── try { var channelConfigSvc = app.Services.GetRequiredService(); 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("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(); // Standard middleware app.UseAuthorization(); app.MapControllers(); app.Run();