Enhance documentation with new features: added dark/light/system theme support, instructions page, and application catalog. Updated API and domain model for app management and automatic migrations on startup. Improved frontend structure with new routes and features for user instructions and app management.

This commit is contained in:
Leonid Pershin
2026-07-01 22:38:01 +03:00
parent d8930409fe
commit 1a8d33efa3
229 changed files with 9226 additions and 20 deletions
@@ -0,0 +1,102 @@
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using PnvPanel.Application.Common.Interfaces;
using PnvPanel.Infrastructure.Identity;
using PnvPanel.Infrastructure.Persistence;
using PnvPanel.Infrastructure.Security;
using PnvPanel.Infrastructure.Xui;
using ThreeXui.ConnectionStrings;
using ThreeXui.Http;
namespace PnvPanel.Infrastructure;
/// <summary>
/// Регистрация сервисов инфраструктуры: EF Core (PostgreSQL), Identity/JWT-аутентификация,
/// XuiPanelGateway, шифрование секретов, (в будущем) SignalR-пуш, фоновые сервисы.
/// </summary>
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
var connectionString = configuration["ConnectionStrings:Default"]
?? throw new InvalidOperationException("Строка подключения 'ConnectionStrings:Default' не сконфигурирована.");
services.AddDbContext<AppDbContext>(options => options.UseNpgsql(connectionString));
services.AddScoped<IAppDbContext>(sp => sp.GetRequiredService<AppDbContext>());
services
.AddIdentityCore<AppUser>(options =>
{
options.User.RequireUniqueEmail = false;
options.Password.RequiredLength = 8;
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.AllowedForNewUsers = true;
})
.AddRoles<AppRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
services.Configure<JwtOptions>(configuration.GetSection(JwtOptions.SectionName));
services.Configure<AdminSeedOptions>(configuration.GetSection(AdminSeedOptions.SectionName));
services.Configure<RolesOptions>(configuration.GetSection(RolesOptions.SectionName));
var jwtOptions = configuration.GetSection(JwtOptions.SectionName).Get<JwtOptions>()
?? throw new InvalidOperationException("Секция конфигурации 'Jwt' не задана.");
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtOptions.Issuer,
ValidateAudience = true,
ValidAudience = jwtOptions.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SigningKey)),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30),
};
});
services.AddAuthorization();
// Шифрование секретов нод at-rest (ASP.NET Core Data Protection). Key-ring — на постоянном
// томе (DataProtection__KeyRingPath), иначе секреты станут нечитаемы при пересоздании контейнера.
var keyRingPath = configuration["DataProtection:KeyRingPath"];
var dataProtectionBuilder = services.AddDataProtection();
if (!string.IsNullOrWhiteSpace(keyRingPath))
dataProtectionBuilder.PersistKeysToFileSystem(new DirectoryInfo(keyRingPath));
services.AddSingleton<IXuiHttpClientFactory, XuiHttpClientFactory>();
// Построители connection string (по одному на протокол) + резолвер по имени протокола.
services.AddSingleton<IXuiConnectionStringBuilder, VlessConnectionStringBuilder>();
services.AddSingleton<IXuiConnectionStringBuilder, VmessConnectionStringBuilder>();
services.AddSingleton<IXuiConnectionStringBuilder, TrojanConnectionStringBuilder>();
services.AddSingleton<IXuiConnectionStringBuilder, ShadowsocksConnectionStringBuilder>();
services.AddSingleton<IXuiConnectionStringBuilderResolver, XuiConnectionStringBuilderResolver>();
services.AddSingleton<ISecretProtector, DataProtectionSecretProtector>();
// Singleton: держит кэш per-node XUI-клиентов между запросами (см. XuiPanelGateway).
services.AddSingleton<IXuiPanelGateway, XuiPanelGateway>();
services.AddScoped<IIdentityService, IdentityService>();
services.AddScoped<IJwtTokenService, JwtTokenService>();
services.AddScoped<IRefreshTokenService, RefreshTokenService>();
services.AddScoped<IRoleService, RoleService>();
services.AddScoped<ICurrentUser, CurrentUser>();
services.AddScoped<DbInitializer>();
return services;
}
}