Refactor project files for improved readability and structure
- Cleaned up whitespace in Directory.Build.props and Directory.Packages.props for consistency. - Reformatted project file references in PnvPanel.Api.csproj for better clarity. - Enhanced code readability in various endpoint files by adjusting line breaks and indentation. - Standardized method signatures and improved formatting in ResultExtensions and multiple endpoint classes for better maintainability.
This commit is contained in:
@@ -20,10 +20,13 @@ using Telegram.Bot;
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Структурное логирование (Serilog), конфигурация из appsettings/env.
|
||||
builder.Services.AddSerilog((services, configuration) => configuration
|
||||
.ReadFrom.Configuration(builder.Configuration)
|
||||
.ReadFrom.Services(services)
|
||||
.Enrich.FromLogContext());
|
||||
builder.Services.AddSerilog(
|
||||
(services, configuration) =>
|
||||
configuration
|
||||
.ReadFrom.Configuration(builder.Configuration)
|
||||
.ReadFrom.Services(services)
|
||||
.Enrich.FromLogContext()
|
||||
);
|
||||
|
||||
// За внешним прокси доверяем X-Forwarded-* (TLS терминируется вне контейнера), но ТОЛЬКО от явно
|
||||
// перечисленных адресов/сетей прокси — иначе клиент может подделать свой IP/схему напрямую, минуя
|
||||
@@ -34,13 +37,25 @@ builder.Services.Configure<ForwardedHeadersOptions>(options =>
|
||||
{
|
||||
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
|
||||
|
||||
foreach (var proxy in builder.Configuration.GetSection("ForwardedHeaders:KnownProxies").Get<string[]>() ?? [])
|
||||
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[]>() ?? [])
|
||||
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])));
|
||||
options.KnownIPNetworks.Add(
|
||||
new System.Net.IPNetwork(IPAddress.Parse(parts[0]), int.Parse(parts[1]))
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -49,6 +64,7 @@ builder.Services.AddApplication();
|
||||
builder.Services.AddInfrastructure(builder.Configuration);
|
||||
|
||||
builder.Services.AddSignalR();
|
||||
|
||||
// В Api, не в Infrastructure — реализации нужен IHubContext<PanelHub>, а Hub определён здесь же.
|
||||
builder.Services.AddSingleton<IRealtimeNotifier, SignalRRealtimeNotifier>();
|
||||
|
||||
@@ -74,17 +90,27 @@ builder.Services.AddSingleton<ITelegramBotClient>(sp =>
|
||||
if (!string.IsNullOrEmpty(proxyUri.UserInfo))
|
||||
{
|
||||
var credentials = proxyUri.UserInfo.Split(':', 2);
|
||||
proxy.Credentials = new NetworkCredential(credentials[0], credentials.Length > 1 ? credentials[1] : string.Empty);
|
||||
proxy.Credentials = new NetworkCredential(
|
||||
credentials[0],
|
||||
credentials.Length > 1 ? credentials[1] : string.Empty
|
||||
);
|
||||
}
|
||||
|
||||
sp.GetRequiredService<ILogger<Program>>().LogInformation(
|
||||
"Telegram bot using proxy {Scheme}://{Host}:{Port}", proxyUri.Scheme, proxyUri.Host, proxyUri.Port);
|
||||
sp.GetRequiredService<ILogger<Program>>()
|
||||
.LogInformation(
|
||||
"Telegram bot using proxy {Scheme}://{Host}:{Port}",
|
||||
proxyUri.Scheme,
|
||||
proxyUri.Host,
|
||||
proxyUri.Port
|
||||
);
|
||||
|
||||
var handler = new SocketsHttpHandler { Proxy = proxy, UseProxy = true };
|
||||
return new TelegramBotClient(token, new HttpClient(handler));
|
||||
});
|
||||
|
||||
// Scoped — зависит от IIdentityService (scoped), не Singleton.
|
||||
builder.Services.AddScoped<ITelegramNotifier, TelegramNotifier>();
|
||||
|
||||
// Singleton — кэширует username бота (getMe) на весь процесс, не из ручного env (см. TelegramBotInfo).
|
||||
builder.Services.AddSingleton<ITelegramBotInfo, TelegramBotInfo>();
|
||||
builder.Services.AddSingleton<PnvBotUpdateHandler>();
|
||||
@@ -92,26 +118,32 @@ builder.Services.AddHostedService<TelegramBotHostedService>();
|
||||
|
||||
builder.Services.AddRateLimiter(options =>
|
||||
{
|
||||
options.AddFixedWindowLimiter(RateLimiting.AuthPolicy, limiterOptions =>
|
||||
{
|
||||
// Настраиваемо через конфиг, чтобы интеграционные тесты (общий TestServer/host на весь
|
||||
// collection, все запросы — от одного "клиента") могли поднять лимит и не ловить 429.
|
||||
limiterOptions.PermitLimit = builder.Configuration.GetValue("RateLimiting:AuthPermitLimit", 20);
|
||||
limiterOptions.Window = TimeSpan.FromMinutes(1);
|
||||
limiterOptions.QueueLimit = 0;
|
||||
});
|
||||
options.AddFixedWindowLimiter(
|
||||
RateLimiting.AuthPolicy,
|
||||
limiterOptions =>
|
||||
{
|
||||
// Настраиваемо через конфиг, чтобы интеграционные тесты (общий TestServer/host на весь
|
||||
// collection, все запросы — от одного "клиента") могли поднять лимит и не ловить 429.
|
||||
limiterOptions.PermitLimit = builder.Configuration.GetValue(
|
||||
"RateLimiting:AuthPermitLimit",
|
||||
20
|
||||
);
|
||||
limiterOptions.Window = TimeSpan.FromMinutes(1);
|
||||
limiterOptions.QueueLimit = 0;
|
||||
}
|
||||
);
|
||||
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
||||
});
|
||||
|
||||
// Энумы сериализуются строками ("Vless", "Active", ...), не числами — самодокументируемый JSON,
|
||||
// корректные строковые литералы при генерации TS-типов из OpenAPI-схемы (см. docs/frontend.md).
|
||||
builder.Services.ConfigureHttpJsonOptions(options =>
|
||||
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()));
|
||||
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter())
|
||||
);
|
||||
|
||||
builder.Services.AddProblemDetails();
|
||||
builder.Services.AddOpenApi();
|
||||
builder.Services.AddHealthChecks()
|
||||
.AddDbContextCheck<AppDbContext>();
|
||||
builder.Services.AddHealthChecks().AddDbContextCheck<AppDbContext>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -120,8 +152,9 @@ var app = builder.Build();
|
||||
if (string.IsNullOrWhiteSpace(builder.Configuration["DataProtection:KeyRingPath"]))
|
||||
{
|
||||
app.Logger.LogWarning(
|
||||
"DataProtection:KeyRingPath is not set — node secret encryption keys are not persistent " +
|
||||
"and will be lost when the container is recreated. Mount a volume and set the path in production.");
|
||||
"DataProtection:KeyRingPath is not set — node secret encryption keys are not persistent "
|
||||
+ "and will be lost when the container is recreated. Mount a volume and set the path in production."
|
||||
);
|
||||
}
|
||||
|
||||
// Авто-применение миграций и идемпотентный сидинг (роли + админ из env) на старте.
|
||||
|
||||
Reference in New Issue
Block a user