Initial import into Gitea

This commit is contained in:
Grae Jones
2026-03-14 13:50:09 -07:00
parent 8e7e03702e
commit 34c1f09e01
154 changed files with 17666 additions and 1548 deletions

View File

@@ -1,4 +1,6 @@
using Azure.Storage.Blobs;
using Gateway.Data;
using Gateway.Models;
using Gateway.ProviderClients;
using Gateway.Security;
using Gateway.Services;
@@ -20,7 +22,18 @@ builder.Services.AddSwaggerGen();
// Data & business services
builder.Services.AddScoped<SqlService>();
builder.Services.AddScoped<ExecutionService>();
// 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>();
@@ -38,8 +51,86 @@ builder.Services.AddHttpClient<GoogleProviderClient>(client =>
// 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
// --------------------
@@ -49,12 +140,31 @@ app.UseSwagger();
app.UseSwaggerUI();
// Health check endpoint (before auth & logging)
app.MapGet("/health", () => Results.Ok(new
app.MapGet("/health", (ChannelConfigService channelSvc, IConfiguration config) =>
{
ok = true,
service = "Gateway",
timestamp = DateTimeOffset.UtcNow
}));
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