using Microsoft.EntityFrameworkCore; using PnvPanel.Application.Common.Interfaces; using PnvPanel.Application.Common.Messaging; using PnvPanel.Application.Common.Models; using PnvPanel.Domain.Audit; using PnvPanel.Domain.Nodes; namespace PnvPanel.Application.Admin.Nodes; public sealed class UpdateNodeCommandHandler( IAppDbContext dbContext, IXuiPanelGateway gateway, ISecretProtector secretProtector, ICurrentUser currentUser ) : ICommandHandler> { public async Task> Handle( UpdateNodeCommand command, CancellationToken cancellationToken ) { var node = await dbContext.Nodes.FirstOrDefaultAsync( n => n.Id == command.NodeId, cancellationToken ); if (node is null) return Result.Failure(NodeErrors.NotFound); if (!Uri.TryCreate(command.BaseAddress, UriKind.Absolute, out var baseAddress)) return Result.Failure(NodeErrors.InvalidBaseAddress); var addressChanged = baseAddress != node.BaseAddress; if (addressChanged) { var validation = gateway.ValidateBaseAddress(baseAddress); if (!validation.IsSuccess) return Result.Failure(validation.Error); } node.UpdateDetails(command.Name, command.Location); if (addressChanged) node.UpdateAddress(baseAddress); if (command.IsEnabled) node.Enable(); else node.Disable(); var credentialsChanged = !string.IsNullOrWhiteSpace(command.Username) && !string.IsNullOrWhiteSpace(command.Password); if (credentialsChanged) { node.UpdateCredentials( new NodeCredentials(command.Username!, secretProtector.Protect(command.Password!)) ); } // Клиент в гейтвее закэширован per-node (адрес+креденшлы захвачены при первом создании) — // при смене любого из них старый закэшированный клиент нужно выбросить, иначе гейтвей // продолжит стучаться по старому адресу/с старым паролем до перезапуска процесса. if (addressChanged || credentialsChanged) gateway.InvalidateClient(node.Id); dbContext.AuditLogs.Add( AuditLog.Create( currentUser.UserId, "NodeUpdated", "Node", node.Id.ToString(), metadata: null, AuditSource.Web ) ); return Result.Success(NodeDto.FromDomain(node)); } }