Initial commit: base slice (auth, roles, users, admin) scaffold
Backend: .NET 10 Clean Architecture + LiteCqrs.Net + EF Core/PostgreSQL + Identity/JWT. Frontend: React 19 + Vite + TanStack Query/Router + Tailwind v4 with a retro CRT theme. Docker/compose deployment mirroring PnvPanel's conventions, scoped down to the current base feature set.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
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);
|
||||
|
||||
// Структурное логирование (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();
|
||||
|
||||
// Раздача статики SPA из wwwroot + fallback на index.html для клиентских маршрутов.
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
app.MapFallbackToFile("index.html");
|
||||
|
||||
app.Run();
|
||||
|
||||
/// <summary>Делает неявный класс Program доступным для WebApplicationFactory<Program> в интеграционных тестах.</summary>
|
||||
public partial class Program;
|
||||
Reference in New Issue
Block a user