131 lines
4.3 KiB
C#
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 { }
|
|
}
|