126 lines
4.6 KiB
C#
126 lines
4.6 KiB
C#
using System.Net;
|
|
using System.Text.Json.Serialization;
|
|
using Microsoft.AspNetCore.HttpOverrides;
|
|
using Microsoft.AspNetCore.RateLimiting;
|
|
using TeleWave.Api.Common;
|
|
using TeleWave.Api.Endpoints;
|
|
using TeleWave.Application;
|
|
using TeleWave.Infrastructure;
|
|
using TeleWave.Infrastructure.Identity;
|
|
using TeleWave.Infrastructure.Persistence;
|
|
using Scalar.AspNetCore;
|
|
using Serilog;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// Загрузка медиа стримится на диск; поднимаем лимит тела запроса Kestrel до максимума загрузки
|
|
// (иначе дефолтные ~30 МБ рубят большие файлы). Собственный контроль размера — в MediaEndpoints.
|
|
builder.WebHost.ConfigureKestrel(options =>
|
|
options.Limits.MaxRequestBodySize =
|
|
builder.Configuration.GetValue<long?>("Media:MaxUploadBytes") ?? 20L * 1024 * 1024 * 1024
|
|
);
|
|
|
|
// Структурное логирование (Serilog), конфигурация из appsettings/env.
|
|
builder.Services.AddSerilog(
|
|
(services, configuration) =>
|
|
configuration
|
|
.ReadFrom.Configuration(builder.Configuration)
|
|
.ReadFrom.Services(services)
|
|
.Enrich.FromLogContext()
|
|
);
|
|
|
|
// За внешним прокси доверяем X-Forwarded-* (TLS терминируется вне контейнера), но ТОЛЬКО от явно
|
|
// перечисленных адресов/сетей прокси — иначе клиент может подделать свой IP/схему напрямую, минуя
|
|
// прокси. По умолчанию (без конфигурации) остаётся дефолт ASP.NET Core — доверие только loopback;
|
|
// для прод-топологии прокси задаётся через ForwardedHeaders__KnownProxies/KnownNetworks (см. .env.example).
|
|
builder.Services.Configure<ForwardedHeadersOptions>(options =>
|
|
{
|
|
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
|
|
|
|
foreach (
|
|
var proxy in builder
|
|
.Configuration.GetSection("ForwardedHeaders:KnownProxies")
|
|
.Get<string[]>()
|
|
?? []
|
|
)
|
|
options.KnownProxies.Add(IPAddress.Parse(proxy));
|
|
|
|
foreach (
|
|
var network in builder
|
|
.Configuration.GetSection("ForwardedHeaders:KnownNetworks")
|
|
.Get<string[]>()
|
|
?? []
|
|
)
|
|
{
|
|
var parts = network.Split('/');
|
|
options.KnownIPNetworks.Add(
|
|
new System.Net.IPNetwork(IPAddress.Parse(parts[0]), int.Parse(parts[1]))
|
|
);
|
|
}
|
|
});
|
|
|
|
builder.Services.AddHttpContextAccessor();
|
|
builder.Services.AddApplication();
|
|
builder.Services.AddInfrastructure(builder.Configuration);
|
|
|
|
builder.Services.AddRateLimiter(options =>
|
|
{
|
|
options.AddFixedWindowLimiter(
|
|
RateLimiting.AuthPolicy,
|
|
limiterOptions =>
|
|
{
|
|
limiterOptions.PermitLimit = builder.Configuration.GetValue(
|
|
"RateLimiting:AuthPermitLimit",
|
|
20
|
|
);
|
|
limiterOptions.Window = TimeSpan.FromMinutes(1);
|
|
limiterOptions.QueueLimit = 0;
|
|
}
|
|
);
|
|
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
|
});
|
|
|
|
// Энумы сериализуются строками, не числами — самодокументируемый JSON.
|
|
builder.Services.ConfigureHttpJsonOptions(options =>
|
|
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter())
|
|
);
|
|
|
|
builder.Services.AddProblemDetails();
|
|
builder.Services.AddOpenApi();
|
|
builder.Services.AddHealthChecks().AddDbContextCheck<AppDbContext>();
|
|
|
|
var app = builder.Build();
|
|
|
|
// Авто-применение миграций и идемпотентный сидинг (роли + админ из env) на старте.
|
|
await app.Services.ApplyMigrationsAsync();
|
|
await app.Services.SeedDataAsync();
|
|
|
|
app.UseForwardedHeaders();
|
|
app.UseSerilogRequestLogging();
|
|
app.UseExceptionHandler();
|
|
|
|
app.UseRateLimiter();
|
|
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
|
|
app.MapOpenApi();
|
|
app.MapScalarApiReference();
|
|
|
|
app.MapHealthChecks("/health");
|
|
|
|
app.MapAuthEndpoints();
|
|
app.MapRoleEndpoints();
|
|
app.MapAdminUserEndpoints();
|
|
app.MapMediaEndpoints();
|
|
|
|
// Раздача статики SPA из wwwroot + fallback на index.html для клиентских маршрутов.
|
|
app.UseDefaultFiles();
|
|
app.UseStaticFiles();
|
|
app.MapFallbackToFile("index.html");
|
|
|
|
app.Run();
|
|
|
|
/// <summary>Делает неявный класс Program доступным для WebApplicationFactory<Program> в интеграционных тестах.</summary>
|
|
public partial class Program;
|