using PnvPanel.Domain.Common; namespace PnvPanel.Domain.Inbounds; /// /// Проекция inbound из 3x-ui. — идентификатор на стороне панели /// (в ThreeXui.Net это string, а не число — так отдаёт API 3x-ui). /// AllowedRoleIds хранит только Guid ролей (не навигацию на AppRole — тот живёт в Infrastructure/Identity, /// Domain не должен на него ссылаться). /// public sealed class Inbound : Entity { public Guid NodeId { get; private set; } public string RemoteInboundId { get; private set; } = string.Empty; public VpnProtocol Protocol { get; private set; } public string Remark { get; private set; } = string.Empty; public int Port { get; private set; } public bool IsPublished { get; private set; } public bool IsAvailable { get; private set; } = true; public string? DisplayName { get; private set; } /// ISO 3166-1 alpha-2 (верхний регистр) — страна, флаг которой пользователь видит перед /// именем инбаунда. Только для отображения, на подключение не влияет. public string? CountryCode { get; private set; } public IReadOnlyList AllowedRoleIds { get; private set; } = []; public DateTimeOffset? LastSyncAt { get; private set; } private Inbound() { } public static Inbound FromRemote( Guid nodeId, string remoteInboundId, VpnProtocol protocol, string remark, int port ) { return new Inbound { Id = Guid.NewGuid(), NodeId = nodeId, RemoteInboundId = remoteInboundId, Protocol = protocol, Remark = remark, Port = port, IsPublished = false, IsAvailable = true, LastSyncAt = DateTimeOffset.UtcNow, }; } public void UpdateFromRemote(VpnProtocol protocol, string remark, int port) { Protocol = protocol; Remark = remark; Port = port; LastSyncAt = DateTimeOffset.UtcNow; // Инбаунд снова пришёл в синхронизации — значит панель его видит, снимаем MarkUnavailable() // (иначе после ЛЮБОГО временного сбоя синка — не только реального удаления — Revoke навсегда // пропускал бы обращение к панели для конфигов на этом инбаунде, хотя клиент в панели жив). IsAvailable = true; } public void Publish( string? displayName, string? countryCode, IReadOnlyCollection allowedRoleIds ) { IsPublished = true; IsAvailable = true; DisplayName = displayName; CountryCode = string.IsNullOrWhiteSpace(countryCode) ? null : countryCode.Trim().ToUpperInvariant(); AllowedRoleIds = allowedRoleIds.Distinct().ToList(); } public void Unpublish() => IsPublished = false; /// Роль удалена админом — снимаем её из списка разрешённых, чтобы не оставлять ссылку на /// несуществующую роль (иначе она навсегда "съедала" бы место в списке, ни на что не влияя, но и /// не позволяя понять, что инбаунд стал доступен меньшему числу ролей, чем задумано). public void RemoveAllowedRole(Guid roleId) => AllowedRoleIds = AllowedRoleIds.Where(id => id != roleId).ToList(); /// /// Инбаунд пропал на панели (не пришёл в очередной синхронизации), но по нему есть конфиги — /// удалить запись нельзя (FK), поэтому помечаем недоступной вместо тихого "не опубликован": /// UI показывает явный статус, а Revoke пропускает обращение к панели как заведомо мёртвое. /// public void MarkUnavailable() { IsAvailable = false; IsPublished = false; } }