using System.Text.Json; using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Settings.Application.Abstractions; using Deal.Modules.Settings.Application.Models; using Deal.Modules.Telegram.Application; using Deal.Modules.Telegram.Application.Models; namespace Deal.Api.Telegram; /// /// Сборка статуса вкладки Telegram — GET /api/tg/status. /// /// Порт-гейт telegram-service (живой статус аккаунта). /// Сервис каталога диалогов ядра (счётчик мониторящихся). /// KV-хранилище настроек тенанта (tgAccount). /// Глобальные ключи приложения Telegram (keysSet). public sealed class TgStatusService( ITelegramGateway gateway, DialogsService dialogs, ISettingsStore settings, TelegramKeysService keys) { private const string IdlePhase = "idle"; private const string ReadyPhase = "ready"; // Опции JSON KV-значений статуса: camelCase (как пишет ингресс) + терпимость регистра. private static readonly JsonSerializerOptions KvJsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true, }; /// /// Форма GET /api/tg/status текущего тенанта. /// /// Полный статус вкладки Telegram. public async Task GetAsync(CancellationToken ct) { TelegramAccountStatusDto live = await ReadLiveAsync(ct).ConfigureAwait(false); // «Подключён» для UI = авторизован (phase ready). Транспортный connected сервиса // означает лишь живость соединения и не гарантирует вход — в UI он даёт «зависание». bool authorized = string.Equals(live.Phase, ReadyPhase, StringComparison.Ordinal); // Живой account (имя из Telegram) приоритетнее KV: при QR-входе KV ещё не заполнен. string account = string.IsNullOrEmpty(live.Account) ? await ReadAccountAsync(ct).ConfigureAwait(false) : live.Account; int monitored = (await dialogs.ListMonitoredIdsAsync(ct).ConfigureAwait(false)).Count; TgKeysSnapshot snapshot = await keys.GetAsync(ct).ConfigureAwait(false); return new TgStatusDto( Phase: live.Phase, Connected: authorized, Listener: live.Listener, Account: account, Monitored: monitored, KeysSet: snapshot.KeysSet, Error: live.Error, QrUrl: live.QrUrl); } private async Task ReadLiveAsync(CancellationToken ct) { try { return await gateway.StatusAsync(ct).ConfigureAwait(false); } catch (Exception exception) when (exception is not OperationCanceledException || !ct.IsCancellationRequested) { return new TelegramAccountStatusDto(IdlePhase, Connected: false, Listener: false, string.Empty, null, null); } } private async Task ReadAccountAsync(CancellationToken ct) { SettingValue? row = await settings.GetAsync(SettingsKeys.TgAccount, ct).ConfigureAwait(false); if (row is null) { return string.Empty; } try { using JsonDocument document = JsonDocument.Parse(row.ValueJson); return document.RootElement.ValueKind == JsonValueKind.String ? document.RootElement.GetString() ?? string.Empty : string.Empty; } catch (JsonException) { return string.Empty; } } }