Files
Deal/src/core/Deal.Api/Telegram/TgStatusService.cs
T
Rustam Khalimov e3a2692507 Добить структуру Api, Contracts, SharedKernel и сервисов
Deal.Api/Http -> Services/Models/Extensions; Contracts/Integrations
и SharedKernel/Tenants -> Abstractions/Models; extension-классы
telegram/ml -> Extensions. namespace/using/FQN мигрированы, using
дедуплицированы.
2026-09-11 13:25:18 +03:00

113 lines
5.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.Settings.Application.Registrars;
using Deal.Modules.Settings.Application.Services;
using Deal.Modules.Telegram.Application;
using Deal.Modules.Telegram.Application.Models;
using Deal.Api.Services;
using Deal.Api.Dtos;
namespace Deal.Api.Telegram;
/// <summary>
/// Сборка статуса вкладки Telegram — GET /api/tg/status (Ruling 8, api-map §4.9 L357359).
/// </summary>
/// <remarks>
/// Форма 1:1 с status() python L103119 в терминах этапа 6:
/// <list type="bullet">
/// <item>live-поля (phase/connected/listener/error/qrUrl) — из гейта <see cref="ITelegramGateway.StatusAsync"/>
/// (живой telegram-service); сервис недоступен/сессии нет (RPC-отказ) → idle-форма (Ruling 8);</item>
/// <item>account — из KV tgAccount (источник истины — ReportStatus ингресса, Ruling 7);</item>
/// <item>monitored — count(Dialogs WHERE Monitor) ядра (DialogsService.ListMonitoredIds);</item>
/// <item>keysSet — оба глобальных ключа приложения заданы оператором (TelegramKeysService, Ruling 3, ТЗ §4.1/§8.1).</item>
/// </list>
/// Scoped: зависимости живут на контекстах запроса (ISettingsStore — схема тенанта, IGlobalSettingsStore — public).
/// </remarks>
/// <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)
{
// Фаза idle-формы (аккаунт не подключён/сервис недоступен — Ruling 8).
private const string IdlePhase = "idle";
// Опции JSON KV-значений статуса: camelCase (как пишет ингресс) + терпимость регистра.
private static readonly JsonSerializerOptions KvJsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
};
/// <summary>
/// Форма GET /api/tg/status текущего тенанта (поля §4.9).
/// </summary>
/// <param name="ct">Токен отмены.</param>
/// <returns>Полный статус вкладки Telegram.</returns>
public async Task<TgStatusDto> GetAsync(CancellationToken ct)
{
TelegramAccountStatusDto live = await ReadLiveAsync(ct).ConfigureAwait(false);
string account = await ReadAccountAsync(ct).ConfigureAwait(false);
int monitored = (await dialogs.ListMonitoredIdsAsync(ct).ConfigureAwait(false)).Count;
TgKeysSnapshot snapshot = await keys.GetAsync(ct).ConfigureAwait(false);
return new TgStatusDto(
Phase: live.Phase,
Connected: live.Connected,
Listener: live.Listener,
Account: account,
Monitored: monitored,
KeysSet: snapshot.KeysSet,
Error: live.Error,
QrUrl: live.QrUrl);
}
// Живой статус из гейта; сбой (сервис недоступен/нет сессии) → idle-форма (Ruling 8).
// ct: Токен отмены.
// Возвращает: Статус гейта либо idle-поля.
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)
{
// «Сервис недоступен → idle-форма» (Ruling 8): connected=false, live-поля пусты. Аккаунт/счётчики
// ядро всё равно докладывает из своего KV/БД (ниже) — как python при отключённом клиенте.
return new TelegramAccountStatusDto(IdlePhase, Connected: false, Listener: false, string.Empty, null, null);
}
}
// Аккаунт «@username» из KV tgAccount (JSON-строка, пишет ReportStatus ингресса, Ruling 7).
// ct: Токен отмены.
// Возвращает: Аккаунт или пустая строка.
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;
}
}
}