Инициализировать репозиторий «Дейл»
Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ, инструкция пользователя, техдокументация, код-стайл), бэклог, скрипты развёртывания и архив прототипа LeadRadar.
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
using System.Text.Json;
|
||||
using Deal.Contracts.Integrations;
|
||||
using Deal.Contracts.Integrations.Models;
|
||||
using Deal.Modules.Settings.Application;
|
||||
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 (Ruling 8, api-map §4.9 L357–359).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Форма 1:1 с status() python L103–119 в терминах этапа 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user