Разбить модули Deal.Modules.* по назначению

Application проектов Discovery, Kanban, Pipeline, Settings, Tenants
разделён на Abstractions/Exceptions/Extensions/Models/Registrars/Services;
namespace приведён к путям, using потребителей мигрированы и
дедуплицированы (169 файлов), cref/FQN обновлены.
This commit is contained in:
Rustam Khalimov
2026-09-11 13:18:14 +03:00
parent 31c434ed93
commit cd0b3b606b
356 changed files with 14436 additions and 12906 deletions
+110 -108
View File
@@ -1,108 +1,110 @@
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 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;
}
}
}
using System.Text.Json;
using Deal.Contracts.Integrations;
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;
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;
}
}
}