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:
@@ -3,4 +3,6 @@ using PnvPanel.Application.Common.Models;
|
||||
|
||||
namespace PnvPanel.Application.Configs.Create;
|
||||
|
||||
public sealed record CreateVpnConfigCommand(Guid InboundId, string? Label) : ICommand<Result<VpnConfigDto>>, IRequiresActivation;
|
||||
public sealed record CreateVpnConfigCommand(Guid InboundId, string? Label)
|
||||
: ICommand<Result<VpnConfigDto>>,
|
||||
IRequiresActivation;
|
||||
|
||||
@@ -8,10 +8,16 @@ using PnvPanel.Domain.Configs;
|
||||
namespace PnvPanel.Application.Configs.Create;
|
||||
|
||||
public sealed class CreateVpnConfigCommandHandler(
|
||||
IAppDbContext dbContext, IIdentityService identityService, IXuiPanelGateway gateway, ICurrentUser currentUser)
|
||||
: ICommandHandler<CreateVpnConfigCommand, Result<VpnConfigDto>>
|
||||
IAppDbContext dbContext,
|
||||
IIdentityService identityService,
|
||||
IXuiPanelGateway gateway,
|
||||
ICurrentUser currentUser
|
||||
) : ICommandHandler<CreateVpnConfigCommand, Result<VpnConfigDto>>
|
||||
{
|
||||
public async Task<Result<VpnConfigDto>> Handle(CreateVpnConfigCommand command, CancellationToken cancellationToken)
|
||||
public async Task<Result<VpnConfigDto>> Handle(
|
||||
CreateVpnConfigCommand command,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
if (currentUser.UserId is not { } userId)
|
||||
return Result.Failure<VpnConfigDto>(AuthErrors.Unauthorized);
|
||||
@@ -20,7 +26,8 @@ public sealed class CreateVpnConfigCommandHandler(
|
||||
if (profile is null)
|
||||
return Result.Failure<VpnConfigDto>(AuthErrors.Unauthorized);
|
||||
|
||||
var inbound = await dbContext.Inbounds.AsNoTracking()
|
||||
var inbound = await dbContext
|
||||
.Inbounds.AsNoTracking()
|
||||
.FirstOrDefaultAsync(i => i.Id == command.InboundId, cancellationToken);
|
||||
|
||||
if (inbound is null || !inbound.IsPublished)
|
||||
@@ -29,19 +36,32 @@ public sealed class CreateVpnConfigCommandHandler(
|
||||
if (!inbound.AllowedRoleIds.Contains(profile.RoleId))
|
||||
return Result.Failure<VpnConfigDto>(ConfigErrors.InboundNotAllowedForRole);
|
||||
|
||||
var node = await dbContext.Nodes.AsNoTracking().FirstOrDefaultAsync(n => n.Id == inbound.NodeId, cancellationToken);
|
||||
var node = await dbContext
|
||||
.Nodes.AsNoTracking()
|
||||
.FirstOrDefaultAsync(n => n.Id == inbound.NodeId, cancellationToken);
|
||||
if (node is null || !node.IsEnabled)
|
||||
return Result.Failure<VpnConfigDto>(ConfigErrors.NodeDisabled);
|
||||
|
||||
var config = VpnConfig.Create(userId, inbound.Id, inbound.Protocol, command.Label);
|
||||
|
||||
var reserveResult = await ReserveQuotaSlotAsync(userId, profile.MaxConfigs, config, cancellationToken);
|
||||
var reserveResult = await ReserveQuotaSlotAsync(
|
||||
userId,
|
||||
profile.MaxConfigs,
|
||||
config,
|
||||
cancellationToken
|
||||
);
|
||||
if (!reserveResult.IsSuccess)
|
||||
return Result.Failure<VpnConfigDto>(reserveResult.Error);
|
||||
|
||||
var addResult = await gateway.AddClientAsync(
|
||||
node, inbound.RemoteInboundId, inbound.Protocol, config.ClientEmail,
|
||||
config.Label ?? config.ClientEmail, profile.MaxIpLimit, cancellationToken);
|
||||
node,
|
||||
inbound.RemoteInboundId,
|
||||
inbound.Protocol,
|
||||
config.ClientEmail,
|
||||
config.Label ?? config.ClientEmail,
|
||||
profile.MaxIpLimit,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
if (!addResult.IsSuccess)
|
||||
{
|
||||
@@ -64,15 +84,26 @@ public sealed class CreateVpnConfigCommandHandler(
|
||||
/// НЕ на время внешнего HTTP-вызова к 3x-ui — иначе рискуем держать соединение к БД открытым
|
||||
/// на секунды под внешним I/O.
|
||||
/// </summary>
|
||||
private async Task<Result> ReserveQuotaSlotAsync(Guid userId, int maxConfigs, VpnConfig config, CancellationToken cancellationToken)
|
||||
private async Task<Result> ReserveQuotaSlotAsync(
|
||||
Guid userId,
|
||||
int maxConfigs,
|
||||
VpnConfig config,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
|
||||
await using var transaction = await dbContext.Database.BeginTransactionAsync(
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
await dbContext.Database.ExecuteSqlInterpolatedAsync(
|
||||
$"SELECT pg_advisory_xact_lock(hashtext({userId.ToString()}))", cancellationToken);
|
||||
$"SELECT pg_advisory_xact_lock(hashtext({userId.ToString()}))",
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
var activeCount = await dbContext.VpnConfigs
|
||||
.CountAsync(c => c.UserId == userId && c.Status == ConfigStatus.Active, cancellationToken);
|
||||
var activeCount = await dbContext.VpnConfigs.CountAsync(
|
||||
c => c.UserId == userId && c.Status == ConfigStatus.Active,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
if (maxConfigs != RoleQuota.Unlimited && activeCount >= maxConfigs)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user