using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.RateLimiting; using OpenTelemetry.Resources; using OpenTelemetry.Trace; using Serilog; using StellaOps.Auth.ServerIntegration; using StellaOps.VexLens.Api; using StellaOps.VexLens.Consensus; using StellaOps.VexLens.Persistence; using StellaOps.VexLens.Persistence.Postgres; using StellaOps.VexLens.Storage; using StellaOps.VexLens.Trust; using StellaOps.VexLens.Verification; using StellaOps.VexLens.WebService.Extensions; using System.Threading.RateLimiting; var builder = WebApplication.CreateBuilder(args); // Configure Serilog Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(builder.Configuration) .Enrich.FromLogContext() .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") .CreateLogger(); builder.Host.UseSerilog(); // Configure OpenAPI builder.Services.AddEndpointsApiExplorer(); builder.Services.AddOpenApi(); // Configure OpenTelemetry builder.Services.AddOpenTelemetry() .ConfigureResource(resource => resource.AddService("StellaOps.VexLens")) .WithTracing(tracing => { tracing .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation(); if (builder.Environment.IsDevelopment()) { tracing.AddConsoleExporter(); } }); // Configure VexLens services builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddScoped(); // Note: PostgreSQL persistence configuration requires VexLens persistence service registration // For now, using in-memory stores configured above // Configure health checks builder.Services.AddHealthChecks(); // Configure rate limiting builder.Services.AddRateLimiter(options => { options.RejectionStatusCode = StatusCodes.Status429TooManyRequests; options.AddFixedWindowLimiter("vexlens", limiterOptions => { limiterOptions.PermitLimit = 100; limiterOptions.Window = TimeSpan.FromMinutes(1); }); }); builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration); builder.TryAddStellaOpsLocalBinding("vexlens"); var app = builder.Build(); app.LogStellaOpsLocalHostname("vexlens"); // Configure request pipeline if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } app.UseStellaOpsCors(); app.UseRateLimiter(); app.UseSerilogRequestLogging(); // Map health check endpoint app.MapHealthChecks("/health", new HealthCheckOptions { ResponseWriter = async (context, report) => { context.Response.ContentType = "application/json"; var result = new { status = report.Status.ToString(), checks = report.Entries.Select(e => new { name = e.Key, status = e.Value.Status.ToString(), description = e.Value.Description }) }; await context.Response.WriteAsJsonAsync(result); } }); // Map VexLens API endpoints app.MapVexLensEndpoints(); // Log startup Log.Information("VexLens WebService starting on {Urls}", string.Join(", ", app.Urls)); try { app.Run(); } catch (Exception ex) { Log.Fatal(ex, "VexLens WebService terminated unexpectedly"); } finally { Log.CloseAndFlush(); } /// /// Null implementation for development without VexHub integration. /// internal sealed class NullVexStatementProvider : IVexStatementProvider { public Task> GetStatementsAsync( string vulnerabilityId, string productKey, string? tenantId, CancellationToken cancellationToken = default) { return Task.FromResult>([]); } }