# Задача 01: Доработка вкладки «Каналы» (UI + API) ## Кратко и по делу Нужно довести вторую вкладку «Каналы» до полностью рабочего состояния на реальных данных сервера. Что должно работать: - список каналов; - вход в канал и чтение сообщений; - вход в тред сообщения (история/ветка); - ответ на сообщение; - лайк/снятие лайка; - подписка на пользователя; - подписка на канал; - видимое имя канала в формате `имя_пользователя/имя_канала`. Запись любых новых сущностей делается через `AddBlock` с подписью на клиенте. Чтение делается через 3 API: - `ListSubscriptionsFeed` - `GetChannelMessages` - `GetMessageThread` Техническая особенность (оставляем как есть): - на экране каналов индикатор непрочитанного = общее число сообщений канала. --- ## Подробное ТЗ ### 1. Цель Сделать рабочий каналовый сценарий «от списка до треда», где чтение строится на RPC API, а запись действий пользователя — только через `AddBlock`. ### 2. Что уже есть в проекте #### 2.1 UI (частично) - Есть страницы: - `channels-list` - `channel-view` - `add-channel-view` - Есть запросы чтения в клиенте: - `authService.listSubscriptionsFeed(...)` - `authService.getChannelMessages(...)` - `authService.getMessageThread(...)` - Есть fallback на mock-данные при ошибках сервера. #### 2.2 API/сервер (уже реализованы) - `ListSubscriptionsFeed` - `GetChannelMessages` - `GetMessageThread` - `AddBlock` #### 2.3 Тесты - Есть интеграционный тест API каналов: `IT_06_ChannelsApi`. - Есть тесты генерации блоков каналов/связей: `IT_03_AddBlock_NoAuth`. - Формат `AddBlock` и его сборка/подпись описаны в `AddBlockSender`. ### 3. Проблемы текущей реализации (что надо закрыть) - Кнопки «подписаться на человека/канал» в списке каналов сейчас UI-only (модалка без реальной записи через `AddBlock`). - `add-channel-view` пока не создает канал на сервере через `AddBlock` (`CreateChannelBody`), только делает `navigate`. - `channel-view` добавляет пост локально (в память), а не отправляет блок `TEXT_POST` через `AddBlock`. - Нет полноценного экрана треда сообщения с реальными `GetMessageThread` и действиями `ответить/лайк/убрать лайк` через блоки. - Нет гарантированного отображения канала в требуемом формате `ownerLogin/channelName`. ### 4. Функциональные требования #### 4.1 Список каналов На вкладке «Каналы» отображать 3 группы: - Мои каналы - Каналы пользователей, на кого я подписан - Каналы, на которые я подписан Источник данных: `ListSubscriptionsFeed`. Каждый канал показывать в формате: - `ownerLogin/channelName` #### 4.2 Открытие канала При входе в канал: - загрузить сообщения через `GetChannelMessages`; - показать список сообщений в хронологическом порядке (по текущему параметру `sort`); - оставить техническую особенность непрочитанных как есть. #### 4.3 Открытие треда сообщения При клике на сообщение: - загрузить тред через `GetMessageThread`; - показать `ancestors`, `focus`, `descendants`; - из треда должны быть доступны действия: - «Ответить» - «Лайк» - «Убрать лайк» Запись действий — только `AddBlock`. #### 4.4 Создание канала В `add-channel-view` кнопка «Создать» должна: - отправлять `AddBlock` с телом `CreateChannelBody`; - после успеха возвращать к списку каналов и обновлять его. #### 4.5 Подписки - Подписка на пользователя: `AddBlock` с `ConnectionBody` подтип `CONNECTION_FOLLOW`, target = HEADER пользователя. - Подписка на канал: `AddBlock` с `ConnectionBody` подтип `CONNECTION_FOLLOW`, target = root блока канала (`CreateChannelBody` или HEADER для канала `0`). ### 5. API (форматы) ## 5.1 ListSubscriptionsFeed (чтение) Request: ```json { "op": "ListSubscriptionsFeed", "requestId": "...", "payload": { "login": "A1", "limit": 200 } } ``` Response (смысловые поля): - `ownedChannels[]` - `followedUsersChannels[]` - `followedChannels[]` --- ## 5.2 GetChannelMessages (чтение) Request: ```json { "op": "GetChannelMessages", "requestId": "...", "payload": { "channel": { "ownerBlockchainName": "A1-001", "channelRootBlockNumber": 0, "channelRootBlockHash": "" }, "limit": 200, "sort": "asc" } } ``` Response (смысловые поля): - `channel` - `messages[]` --- ## 5.3 GetMessageThread (чтение) Request: ```json { "op": "GetMessageThread", "requestId": "...", "payload": { "message": { "blockchainName": "A1-001", "blockNumber": 15, "blockHash": "..." }, "depthUp": 20, "depthDown": 2, "limitChildrenPerNode": 50 } } ``` Response (смысловые поля): - `ancestors[]` - `focus` - `descendants[]` --- ## 5.4 AddBlock (запись) Любое изменение (создать канал, пост, reply, реакция, подписка) записывается через: ```json { "op": "AddBlock", "requestId": "...", "payload": { "blockchainName": "A1-001", "blockNumber": 6, "prevBlockHash": "<64-hex>", "blockBytesB64": "" } } ``` Важно: - `blockBytesB64` формируется на клиенте. - Подпись блока формируется на клиенте приватным blockchain key пользователя. - Перед добавлением блока клиент берет актуальный курсор цепочки с сервера. ### 6. Типы блоков для каналов и связей (через AddBlock) - Создание канала: `CreateChannelBody` - Пост/ответ: `TextBody` (`TEXT_POST`, `TEXT_REPLY`) - Реакции: `ReactionBody` (лайк/снятие лайка) - Подписки: `ConnectionBody` (`CONNECTION_FOLLOW`) ### 7. Критерии приемки - Список каналов отображается с реальными данными API. - Формат названия канала в UI: `ownerLogin/channelName`. - Создание канала реально пишет блок и канал появляется после обновления. - Отправка поста/ответа/реакций реально пишет блок и видна после перечитки API. - Подписка на пользователя/канал реально пишет блок и отражается в выдаче. - Переход в тред сообщения показывает реальные `ancestors/focus/descendants`. - Непрочитанные в списке каналов = общее число сообщений (временное правило). ### 8. Локальный запуск (уже сделано) Команда: ```bash ./gradlew startLocal ``` Что делает: - чистит логи; - билдит сервер; - запускает локальный WS сервер; - запускает локальный HTTP сервер клиента; - открывает браузер по URL с параметром `localWsPort`.