# 07. Channels Feature Runbook (человеческое описание + диагностика) ## 1) Что уже сделано простыми словами Сейчас реализован полный минимальный контур для каналов: 1. **Серверные read API**: - `ListSubscriptionsFeed` — экран списка каналов. - `GetChannelMessages` — сообщения конкретного канала. - `GetMessageThread` — дерево обсуждения для сообщения. - `GetMessageLikes` — списки пользователей, поставивших лайк сообщению. - `SetChannelReadState` — подписанный watermark чтения канала. 2. **UI вкладки Каналы**: - при открытии пытается загрузить реальный feed с сервера; - если сервер недоступен — fallback на мок-данные; - группы каналов выводятся в нужном порядке; - есть кнопка «Добавить канал», модалки подписки, переход в канал. 3. **Проверка уникальности имени канала на сервере** - в `AddBlock` при `CreateChannelBody` добавлена проверка; - при дубле возвращается `409 channel_name_already_exists`. --- ## 2) Что тестировать в первую очередь (быстрый чеклист) ### Базовый smoke 1. Авторизоваться в UI. 2. Открыть вкладку «Каналы». 3. Убедиться, что данные загрузились с сервера (или виден fallback-баннер). 4. Нажать любой канал — должен открыться экран канала с сообщениями. ### API smoke 1. Вызвать `ListSubscriptionsFeed`. 2. Для канала `ownedChannels[0]` вызвать `GetChannelMessages`. 3. Для первого `messages[0]` вызвать `GetMessageThread`. 4. Для первого `messages[0]` вызвать `GetMessageLikes`. 5. Для подписанного канала вызвать `SetChannelReadState` с текущим `messagesCount`. ### Ошибки 1. `ListSubscriptionsFeed` с пустым login -> `bad_fields`. 2. `GetChannelMessages` с битым channel payload -> `bad_fields`. 3. `GetMessageThread` с несуществующим block -> `message_not_found`. 4. `GetMessageLikes` с битым `message.blockHash` -> `bad_hash`. 5. `SetChannelReadState` без активной подписки -> `CHANNEL_NOT_FOLLOWED`. 6. `AddBlock(CreateChannel)` с уже существующим именем -> `channel_name_already_exists`. --- ## 3) Готовые JSON-запросы для ручной диагностики ## 3.1 ListSubscriptionsFeed ```json { "op": "ListSubscriptionsFeed", "requestId": "debug-feed-1", "payload": { "login": "TestUser1", "limit": 200 } } ``` ## 3.2 GetChannelMessages ```json { "op": "GetChannelMessages", "requestId": "debug-ch-1", "payload": { "channel": { "ownerBlockchainName": "TestUser1-001", "channelRootBlockNumber": 0, "channelRootBlockHash": "" }, "limit": 200, "sort": "asc" } } ``` ## 3.3 GetMessageThread ```json { "op": "GetMessageThread", "requestId": "debug-thread-1", "payload": { "message": { "blockchainName": "TestUser1-001", "blockNumber": 123, "blockHash": "" }, "depthUp": 20, "depthDown": 2, "limitChildrenPerNode": 50 } } ``` ## 3.4 GetMessageLikes ```json { "op": "GetMessageLikes", "requestId": "debug-likes-1", "payload": { "message": { "blockchainName": "TestUser1-001", "blockNumber": 123, "blockHash": "" } } } ``` ## 3.5 SetChannelReadState ```json { "op": "SetChannelReadState", "requestId": "debug-read-state-1", "payload": { "login": "TestUser1", "owner_bch_name": "TestUser2-001", "channel_name": "news", "read_count": 25, "time_ms": 1760000000000, "client_key": "", "signature": "" } } ``` --- ## 4) Что смотреть в ответах ### ListSubscriptionsFeed - `payload.login` — канонический login. - `ownedChannels / followedUsersChannels / followedChannels` — массивы. - у каждой записи есть: - `channel.channelRoot.blockNumber`, - `messagesCount`, - `unreadCount`, - `readStateInitialized`, - `lastMessage` (может быть null, если сообщений нет). ### GetChannelMessages - `payload.channel` заполнен; - `payload.messages[]` содержит: - `likesCount`, `repliesCount`, - `versionsTotal`, `versions[]`, - `text` должен быть текущей (последней) версией. ### GetMessageThread - `payload.ancestors[]`, `payload.focus`, `payload.descendants[]`. - у узлов должны быть версии и счетчики. - у каждого узла дополнительно может приходить `rawBlockB64` (Base64 сырого `block_bytes`). ### GetMessageLikes - `payload.shining[]`, `payload.official[]`, `payload.others[]` — группы пользователей. - у каждого пользователя есть `login`, `firstName`, `lastName`, `avatarAr`. - `payload.truncated` сейчас всегда `false`; поле оставлено для совместимости и будущей пагинации. ### SetChannelReadState - `payload.read_count` — сохраненная позиция чтения. - `payload.unread_count` — пересчитанный unread для канала. - `payload.applied=false` означает, что на сервере уже была более новая signed-позиция. ### Важно по совместимости - `rawBlockB64` добавлен только в `GetMessageThread`. - `GetChannelMessages` не содержит `rawBlockB64` (без изменений формата ленты). --- ## 5) Частые проблемы и как быстро локализовать 1. **`status != 200`, code=bad_fields** - проверить вложенность payload и обязательные поля. 2. **`message_not_found` в GetMessageThread** - обычно передали blockNumber/hash не из `messageRef`. 3. **Пустой список сообщений в GetChannelMessages** - проверить `ownerBlockchainName` и `channelRootBlockNumber`. 4. **`channel_name_already_exists` при AddBlock** - это ожидаемо: в этой цепочке уже есть канал с таким именем. --- ## 6) Для будущей доработки 1. Добавить курсоры (пагинацию) для больших каналов. 2. Перевести «Подписаться»/«Добавить канал» в UI с демо-заглушек на реальные write RPC. 3. Добавить batch-агрегации для thread/versions (оптимизация). 4. Добавить полноценные интеграционные тесты на негативные кейсы и нагрузку.