Files
Deal/docs/superpowers/specs/2026-09-11-source-attachments-вопросы.md
T
Rustam Khalimov 27c7831910
ci / build-test (push) Canceled after 0s
Deal — единая кодовая база
SaaS-мониторинг Telegram: ядро (модули Cards/Kanban/Pipeline/Tenants/Settings/
Discovery, Api, Infrastructure), сервисы telegram/ai/ml/storage, фронт Vue,
контракты и grpc-hosting, деплой-конфиги (dev/prod/observability/CI-раннер),
Gitea Actions CI, документация (ТЗ, техдок, api-map, код-стайл, планы, бэклог).

Текущее состояние: все этапы роадмапа 0–12 закрыты, сборка 5 sln 0/0,
тесты 1340/130/52/38/9 зелёные.
2026-09-11 23:56:47 +03:00

8.6 KiB
Raw Blame History

Открытые вопросы: вложения источников (media → Storage) и просмотр исходника

Дата: 2026-09-11. Статус: решения владельца получены (см. §0).

0. Решения владельца (2026-09-11)

  • А) Медиа-посты пропускаем. Сообщения без текста (только медиа/вложение) в систему не попадают. Извлечение вложений Telegram и выгрузка их в Storage не делаются. Generic-контракт по-прежнему умеет нести DataRef — этим смогут пользоваться другие источники (файл/диск/таблица) и ручные вложения карточки.
  • Б) Проверка без живого Telegram — реализуем с юнит-тестами на фейковой сессии/фейковом Storage, без реального API.
  • В) Объяснение термина — в §1.5. Решение: делаем. Реализован remote-просмотр: TelegramService.ReadSource, ITelegramGateway.ReadSourceAsync, TelegramSourceContentProvider (Kind=telegram) в ядре, GET /api/cards/{id}/source и кнопка «Обновить из источника» в подробной карточке.

1.5. Что такое «remote-просмотр исходника»

Карточка хранит ссылку на источник (SourceRef) и содержимое (SourceContent). Содержимое попадает в карточку в момент приёма. «Просмотр исходника» — это возможность по кнопке догрузить/показать оригинальное сообщение у источника (то, что было в канале/письме/строке), если контент в карточке устарел или урезан.

Сейчас содержимое уже отдаётся в CardDto.content и через GET /api/cards/{id}/source. Для локальных источников этого достаточно. Для внешних источников (например Telegram) данные лежат не в ядре, а в сервисе-владельце; чтобы их догрузить, ядру нужен провайдер ISourceContentProvider для kind, который ходит по gRPC к сервису-владельцу (условный RPC ReadSource(dialogId, msgId)) и возвращает исходный текст/медиа.

Это и есть «remote-просмотр» — расширение extension-point, которое не требуется до появления реальной необходимости (напр. если карточки хранят урезанный текст или нужно открыть живой первоисточник).

1. Что уже готово (не требует решений)

  • Единый контракт источника несёт вложения: SourceContent.Data: IReadOnlyList<DataRef> — ссылки на объекты Storage-сервиса (DataRef.Id/Ref/Kind/MimeType/...).
  • Контракт входящего потока (src/contracts/sources.proto, PushSource) передаёт DataRefProto/ContactRefProto — источник может прислать вложения сразу со ссылками.
  • Storage-сервис (src/storage-service/Deal.Storage, storage.proto) умеет Upload/Download/Stat/Delete, сам определяет kind/mimeType/размеры (контент-снифинг), бэкенд — MinIO.
  • Ядро хранит SourceContent карточки (в т.ч. Data) и отдаёт его в CardDto.content и через GET /api/cards/{id}/source (extension-point ISourceContentProvider + SourceContentResolver).
  • Фронт рендерит вложения: SourceContentView.vue (image/video/audio/document/archive по kind), ссылки и контакты — списками.

2. Проблемная часть (требует живого Telegram)

Извлечение и выгрузка медиа из Telegram не проверяемы офлайн:

  1. Медиа-сообщения сейчас отбрасываются. TlMessageMapper.ToMessage (src/telegram-service/Deal.Telegram/Telegram/TlMessageMapper.cs) принимает только Message с непустым message (текстом). Посты с одним вложением и подписью (media + caption) не попадают в поток вообще. Нужно: определять Message.media, читать caption, тип/размеры/длительность.
  2. Скачивание и выгрузка. Требуется client.DownloadMedia(...) (WTelegram) → поток → StorageService.Upload(meta + data)DataRefProto. В telegram-сервисе нет gRPC-клиента Storage и соответствующей конфигурации в compose (endpoint/токен). Проверить можно только с реальным Telegram-аккаунтом и живым MinIO.
  3. Подпись без текста. Даже если вложение извлечено, в посте может не быть текста: нужен ли такой пост «карточкой» (сейчас PipelineIngestService пропускает записи без Content.Text)? Предлагается принимать запись, если есть текст или вложения/ссылки/контакты, а классификацию медиа-онли строить по подписи (caption) и метаданным. Требуется подтверждение продуктовой логики.
  4. Просмотр исходника из другого контура. «Открыть исходник» для remote-источников (Telegram — это лишь один из них) требует провайдера ISourceContentProvider, который ходит по gRPC к сервису-владельцу источника (новый RPC, например ReadSource(dialogId, msgId)), возвращая текст/медиа. Это тоже живой Telegram.

3. Предлагаемый план (после подтверждения)

  1. TelegramMessage расширить моделью TelegramAttachment (caption, fileName, mimeType, size, width, height, durationSec, Task<Stream> Open(cancellationToken)), заполнять в TlMessageMapper из Message.media/Document/Photo.
  2. В telegram-сервисе добавить StorageClient (gRPC, Deal.Grpc.Storage) + SourceAttachmentUploader: загрузка каждого вложения → DataRefProto.
  3. DialogProtoMapper.ToSourceRequest(message, dataRefs) — прокинуть content.data и caption.
  4. PipelineIngestService: принимать запись при непустом тексте или непустых Data/Links/Contacts (нужно продуктовое решение по п.2.3).
  5. ISourceContentProvider для kind="telegram" — gRPC-провайдер к telegram-сервису (RPC ReadSource).
  6. Настройки: endpoint/токен Storage в deploy/compose.dev.yml/compose.prod.yml для telegram-сервиса.

4. Что нужно от владельца

  • А) Делать ли медиа-сообщения без текста карточками (по подписи/метаданным), или пропускать?
  • Б) Для проверки вложений нужны живые Telegram api_id/api_hash и работающий MinIO — будет ли прогон на вашей стороне, или реализуем «слепо» с юнит-тестами на фейковой сессии и фейковом Storage?
  • В) Нужен ли remote-просмотр исходника (ReadSource) в этом объёме, или достаточно того, что содержимое хранится в карточке?

Пока эти пункты не закрыты, они вынесены в backlog.md (TD-STORE-ATTACH, TD-SOURCE-PROVIDER), а generic-часть (контракт, Storage-сервис, хранение, API, рендер) реализована.