Files
git.stella-ops.org/src/ReachGraph/StellaOps.ReachGraph.WebService/Program.cs

131 lines
4.3 KiB
C#

// Licensed to StellaOps under the BUSL-1.1 license.
using StellaOps.Auth.ServerIntegration;
using Microsoft.AspNetCore.RateLimiting;
using Npgsql;
using StackExchange.Redis;
using StellaOps.ReachGraph.Cache;
using StellaOps.ReachGraph.Hashing;
using StellaOps.ReachGraph.Persistence;
using StellaOps.ReachGraph.Serialization;
using StellaOps.ReachGraph.WebService.Services;
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new()
{
Title = "ReachGraph Store API",
Version = "v1",
Description = "Content-addressed storage for reachability subgraphs"
});
});
// PostgreSQL (lazy so integration tests can replace before first resolve)
builder.Services.AddSingleton<NpgsqlDataSource>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
var connStr = config.GetConnectionString("PostgreSQL")
?? throw new InvalidOperationException("PostgreSQL connection string not configured");
return new NpgsqlDataSourceBuilder(connStr).Build();
});
// Redis/Valkey (lazy so integration tests can replace before first resolve)
builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
var redisConnStr = config.GetConnectionString("Redis") ?? "localhost:6379";
return ConnectionMultiplexer.Connect(redisConnStr);
});
// Core services
builder.Services.AddSingleton<CanonicalReachGraphSerializer>();
builder.Services.AddSingleton<ReachGraphDigestComputer>();
// Persistence
builder.Services.AddScoped<IReachGraphRepository, PostgresReachGraphRepository>();
// Cache
builder.Services.Configure<ReachGraphCacheOptions>(
builder.Configuration.GetSection("ReachGraphCache"));
builder.Services.AddScoped<IReachGraphCache>(sp =>
{
var redisMultiplexer = sp.GetRequiredService<IConnectionMultiplexer>();
var serializer = sp.GetRequiredService<CanonicalReachGraphSerializer>();
var options = Microsoft.Extensions.Options.Options.Create(
builder.Configuration.GetSection("ReachGraphCache").Get<ReachGraphCacheOptions>()
?? new ReachGraphCacheOptions());
var logger = sp.GetRequiredService<ILogger<ReachGraphValkeyCache>>();
// TODO: Get tenant from request context
return new ReachGraphValkeyCache(redisMultiplexer, serializer, options, logger, "default");
});
// Application services
builder.Services.AddScoped<IReachGraphStoreService, ReachGraphStoreService>();
builder.Services.AddScoped<IReachGraphSliceService, ReachGraphSliceService>();
builder.Services.AddScoped<IReachGraphReplayService, ReachGraphReplayService>();
// Rate limiting
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.AddPolicy("reachgraph-read", ctx =>
RateLimitPartition.GetFixedWindowLimiter(
ctx.User.FindFirst("tenant")?.Value ?? ctx.Request.Headers["X-Tenant-ID"].FirstOrDefault() ?? "anonymous",
_ => new FixedWindowRateLimiterOptions
{
Window = TimeSpan.FromMinutes(1),
PermitLimit = 100
}));
options.AddPolicy("reachgraph-write", ctx =>
RateLimitPartition.GetFixedWindowLimiter(
ctx.User.FindFirst("tenant")?.Value ?? ctx.Request.Headers["X-Tenant-ID"].FirstOrDefault() ?? "anonymous",
_ => new FixedWindowRateLimiterOptions
{
Window = TimeSpan.FromMinutes(1),
PermitLimit = 20
}));
});
// Response compression
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
});
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
builder.TryAddStellaOpsLocalBinding("reachgraph");
var app = builder.Build();
app.LogStellaOpsLocalHostname("reachgraph");
// Configure pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseResponseCompression();
app.UseStellaOpsCors();
app.UseRateLimiter();
app.UseAuthorization();
app.MapControllers();
app.Run();
// Make Program class accessible for integration testing
namespace StellaOps.ReachGraph.WebService
{
public partial class Program { }
}