using Microsoft.EntityFrameworkCore; using PnvPanel.Application.Auth; using PnvPanel.Application.Common.Interfaces; using PnvPanel.Application.Common.Messaging; using PnvPanel.Application.Common.Models; using PnvPanel.Domain.Configs; namespace PnvPanel.Application.Configs.Rotate; public sealed class RotateVpnConfigCommandHandler( IAppDbContext dbContext, IXuiPanelGateway gateway, IIdentityService identityService, ICurrentUser currentUser ) : ICommandHandler> { public async Task> Handle( RotateVpnConfigCommand command, CancellationToken cancellationToken ) { if (currentUser.UserId is not { } userId) return Result.Failure(AuthErrors.Unauthorized); var config = await dbContext.VpnConfigs.FirstOrDefaultAsync( c => c.Id == command.ConfigId && c.UserId == userId, cancellationToken ); if (config is null) return Result.Failure(ConfigErrors.NotFound); if (config.Status != ConfigStatus.Active) return Result.Failure(ConfigErrors.NotFound); var inbound = await dbContext .Inbounds.AsNoTracking() .FirstOrDefaultAsync(i => i.Id == config.InboundId, cancellationToken); if (inbound is null) return Result.Failure(ConfigErrors.InboundNotAvailable); var node = await dbContext .Nodes.AsNoTracking() .FirstOrDefaultAsync(n => n.Id == inbound.NodeId, cancellationToken); if (node is null) return Result.Failure(ConfigErrors.NodeDisabled); var profile = await identityService.GetProfileAsync(userId, cancellationToken); if (profile is null) return Result.Failure(AuthErrors.Unauthorized); var newClientEmail = VpnConfig.GenerateClientEmail(userId); var addResult = await gateway.AddClientAsync( node, inbound.RemoteInboundId, config.Protocol, newClientEmail, config.Label ?? newClientEmail, profile.MaxIpLimit, cancellationToken ); if (!addResult.IsSuccess) return Result.Failure(addResult.Error); var oldClientExternalId = config.ClientExternalId; config.Rotate(newClientEmail, addResult.Value); await dbContext.SaveChangesAsync(cancellationToken); // Старого клиента удаляем ПОСЛЕ коммита нового состояния: если удаление не выйдет, // у пользователя просто останется лишний нерабочий-для-него клиент в панели — не критично. await gateway.RemoveClientAsync( node, inbound.RemoteInboundId, oldClientExternalId, config.Protocol, cancellationToken ); return Result.Success(VpnConfigDto.FromDomain(config, inbound)); } }