Files
Deal/src/core/Deal.Api/Telegram/TgStatusService.cs
T
stepan a9f2b3a1ef
ci / build-test (pull_request) Failing after 1m58s
Починить QR-вход и реактивный статус Telegram
tgStart ждёт старт QR до показа картинки (устранена гонка с 404). Вкладка Telegram опрашивает статус и обновляет QR по таймеру, не останавливаясь до авторизации. Статус: account берётся из живого статуса сервиса, connected = phase ready (авторизация), а не транспорт.
2026-09-13 16:22:20 +03:00

95 lines
4.0 KiB
C#

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;
/// <summary>
/// Сборка статуса вкладки Telegram — GET /api/tg/status.
/// </summary>
/// <param name="gateway">Порт-гейт telegram-service (живой статус аккаунта).</param>
/// <param name="dialogs">Сервис каталога диалогов ядра (счётчик мониторящихся).</param>
/// <param name="settings">KV-хранилище настроек тенанта (tgAccount).</param>
/// <param name="keys">Глобальные ключи приложения Telegram (keysSet).</param>
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,
};
/// <summary>
/// Форма GET /api/tg/status текущего тенанта.
/// </summary>
/// <returns>Полный статус вкладки Telegram.</returns>
public async Task<TgStatusDto> 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<TelegramAccountStatusDto> 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<string> 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;
}
}
}