Добавить remote-просмотр исходника карточки

RPC TelegramService.ReadSource + ISessionClient.GetMessageAsync, порт ITelegramGateway.ReadSourceAsync и провайдер TelegramSourceContentProvider в ядре; GET /api/cards/{id}/source догружает исходник у сервиса-владельца, в UI — кнопка «Обновить из источника». Медиа-посты пропускаются.
This commit is contained in:
Rustam Khalimov
2026-09-11 17:22:06 +03:00
parent c06ee1cf79
commit db4455a554
30 changed files with 742 additions and 10 deletions
@@ -278,6 +278,30 @@ public sealed class GrpcTelegramClient : ITelegramGateway
}
}
/// <inheritdoc />
public async Task<TelegramSourceContentDto> ReadSourceAsync(
string dialogId,
long msgId,
CancellationToken ct)
{
TenantId tenantId = RequireTenant();
try
{
ReadSourceReply reply = await CallAsync(
tenantId, TimeSpan.FromSeconds(CommandDeadlineSeconds), ct,
(client, options) => client.ReadSourceAsync(
new ReadSourceRequest { DialogId = dialogId ?? string.Empty, MsgId = msgId }, options));
return new TelegramSourceContentDto(
reply.Found,
reply.HasText ? reply.Text : null,
reply.HasTime ? reply.Time : null);
}
catch (Exception exception)
{
throw TranslateTransportFailure(exception, tenantId, "read_source");
}
}
/// <inheritdoc />
public async Task<IReadOnlyList<TelegramDialogEntryDto>> SearchAsync(
string query,
@@ -67,6 +67,13 @@ public sealed class LocalTelegramGateway : ITelegramGateway
CancellationToken ct)
=> Task.FromResult<IReadOnlyList<TelegramRecentMessageDto>>([]);
/// <inheritdoc />
public Task<TelegramSourceContentDto> ReadSourceAsync(
string dialogId,
long msgId,
CancellationToken ct)
=> Task.FromResult(new TelegramSourceContentDto(false, null, null));
/// <inheritdoc />
public Task<IReadOnlyList<TelegramDialogEntryDto>> SearchAsync(
string query,
@@ -0,0 +1,39 @@
using System.Globalization;
using Deal.Contracts.Integrations.Abstractions;
using Deal.Contracts.Integrations.Models;
using Deal.Modules.Cards.Application.Sources;
namespace Deal.Infrastructure.Integrations.Sources;
/// <summary>
/// Провайдер содержимого исходника источника telegram: запрос к telegram-service по id сообщения.
/// </summary>
/// <param name="gateway">Порт-гейт к telegram-service.</param>
public sealed class TelegramSourceContentProvider(ITelegramGateway gateway) : ISourceContentProvider
{
private const string TelegramKind = "telegram";
/// <inheritdoc />
public string Kind => TelegramKind;
/// <inheritdoc />
public async Task<SourceContent?> LoadAsync(SourceRef source, CancellationToken ct)
{
if (!string.Equals(source.Kind, TelegramKind, StringComparison.OrdinalIgnoreCase))
{
return null;
}
string dialogId = source.OriginRef ?? string.Empty;
if (dialogId.Length == 0
|| !long.TryParse(source.ExternalId, NumberStyles.Integer, CultureInfo.InvariantCulture, out long msgId))
{
return null;
}
TelegramSourceContentDto content = await gateway.ReadSourceAsync(dialogId, msgId, ct).ConfigureAwait(false);
return content.Found
? new SourceContent { Text = content.Text }
: null;
}
}
@@ -3,12 +3,14 @@ using Deal.Infrastructure.Integrations.Abstractions;
using Deal.Infrastructure.Integrations.Models;
using Deal.Infrastructure.Integrations.Options;
using Deal.Infrastructure.Integrations.Services;
using Deal.Infrastructure.Integrations.Sources;
using Deal.Infrastructure.Persistence;
using Deal.Infrastructure.Persistence.Repositories;
using Deal.Infrastructure.Security;
using Deal.Infrastructure.Services;
using Deal.Infrastructure.Tenancy;
using Deal.Modules.Cards.Application.Abstractions;
using Deal.Modules.Cards.Application.Sources;
using Deal.Modules.Discovery.Application.Abstractions;
using Deal.Modules.Kanban.Application.Abstractions;
using Deal.Modules.Pipeline.Application.Abstractions;
@@ -56,6 +58,8 @@ public static class ServiceCollectionExtensions
services.AddScoped<ICardStore, KanbanStore>();
services.AddScoped<ISourceContentProvider, TelegramSourceContentProvider>();
services.AddScoped<IPipelineStore, PipelineStore>();
services.AddScoped<IMlLearningStore, MlLearningStore>();