From 0c7d8fac02539e181b04c61899e18158de213100d848d86619196bb7e277d5b6 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Tue, 7 Apr 2026 14:43:08 +0300 Subject: [PATCH] 07-04-2026 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Сделал вкладку параметры пользователя РАБОТАЮЩЕЙ Добавил - Локальный запуск - техническое задание 1 (доработать ui что бы RFYFKS работали) --- TASKS/01-07.04.26-каналы-api/01-ТЗ-каналы.md | 220 ++++++++++++++++++ .../01-кратко-для-внешних.md | 25 ++ TASKS/CHAT_PWA_HANDOFF.md | 203 ---------------- build.gradle | 1 + .../blockchain/Net_AddBlock_Handler.java | 1 + task/2.md | 31 --- 6 files changed, 247 insertions(+), 234 deletions(-) create mode 100644 TASKS/01-07.04.26-каналы-api/01-ТЗ-каналы.md create mode 100644 TASKS/01-07.04.26-каналы-api/01-кратко-для-внешних.md delete mode 100644 TASKS/CHAT_PWA_HANDOFF.md delete mode 100644 task/2.md diff --git a/TASKS/01-07.04.26-каналы-api/01-ТЗ-каналы.md b/TASKS/01-07.04.26-каналы-api/01-ТЗ-каналы.md new file mode 100644 index 0000000..22688de --- /dev/null +++ b/TASKS/01-07.04.26-каналы-api/01-ТЗ-каналы.md @@ -0,0 +1,220 @@ +# Задача 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`. diff --git a/TASKS/01-07.04.26-каналы-api/01-кратко-для-внешних.md b/TASKS/01-07.04.26-каналы-api/01-кратко-для-внешних.md new file mode 100644 index 0000000..e8c7e83 --- /dev/null +++ b/TASKS/01-07.04.26-каналы-api/01-кратко-для-внешних.md @@ -0,0 +1,25 @@ +# Краткое описание задачи + +Нужно сделать полностью рабочую вкладку «Каналы» в SHiNE. + +Пользователь должен: +- видеть список каналов; +- открывать канал и читать сообщения; +- открывать тред сообщения; +- отвечать, ставить и убирать лайк; +- подписываться на пользователей и каналы. + +Чтение данных идет через 3 API: +- `ListSubscriptionsFeed` +- `GetChannelMessages` +- `GetMessageThread` + +Все действия записи делаются только через `AddBlock` с подписью на клиенте. + +Формат имени канала в интерфейсе: +- `имя_пользователя/имя_канала` + +Локальный запуск проекта: +```bash +./gradlew startLocal +``` diff --git a/TASKS/CHAT_PWA_HANDOFF.md b/TASKS/CHAT_PWA_HANDOFF.md deleted file mode 100644 index 630e288..0000000 --- a/TASKS/CHAT_PWA_HANDOFF.md +++ /dev/null @@ -1,203 +0,0 @@ -# Анонс для исполнителя (кратко) - -Ниже описан текущий статус ветки по функциям: -- PWA + FCM уведомления -- личные сообщения (вторая слева вкладка, "Личные сообщения") -- связи / близкие друзья (центральная вкладка) -- server-push события (в том числе закрытие сессии) - -## Что нужно проверить и довести до рабочего состояния -1. **PWA/FCM**: регистрация service worker, получение FCM token, отправка token на сервер, получение foreground/background уведомлений. -2. **Личные сообщения**: отправка первого сообщения любому пользователю, входящие по WS, ACK, fallback в FCM, дедупликация на клиенте. -3. **Связи / близкие друзья**: поиск пользователя, добавление в близкие, обновление графа связей сразу после добавления. -4. **SessionRevoked**: если сессию закрыли с другого устройства, клиент должен корректно завершить локальную сессию и показать понятное состояние. - -## Главная цель handoff -Сделать так, чтобы все описанные сценарии стабильно работали end-to-end без ручных "подпинок". - ---- - -# Подробное ТЗ и технические детали - -## 1) Архитектура протокола и общие принципы - -### 1.1 Формат обмена -Используется JSON-over-WebSocket. -- Для запрос-ответ: `op + requestId + payload` -- Для server-push событий: те же поля, но `event=true`, `requestId` используется как eventId. - -### 1.2 ID сообщений/событий -ID генерируются в формате: -- `prefix-yyyyMMdd-HHmmss-SSS-random10` - -Реализация: `NetIdGenerator`. - -### 1.3 Роли каналов доставки -- **WS** — приоритетная доставка в активные сессии. -- **FCM** — fallback, если ACK по WS не получен или WS-сессия отсутствует. - ---- - -## 2) PWA + FCM - -## 2.1 Что уже добавлено -### Клиент -- `shine-UI/manifest.webmanifest` -- `shine-UI/firebase-messaging-sw.js` -- `shine-UI/js/services/pwa-push-service.js` -- `shine-UI/index.html` содержит placeholders: - - `window.__SHINE_FIREBASE_CONFIG__` - - `window.__SHINE_FIREBASE_VAPID_KEY__` - -### Сервер -- API `UpsertPushToken` -- Таблица `user_push_tokens` -- Серверный FCM sender (legacy HTTP): `FcmPushSender` -- Конфиг: `fcm.server.key` в `application.properties` - -## 2.2 Как должно работать (целевое поведение) -1. Клиент после авторизации регистрирует SW. -2. Просит permission на notifications. -3. Получает FCM token. -4. Если token новый/изменился — отправляет `UpsertPushToken`. -5. Сервер сохраняет token за конкретной сессией. - -## 2.3 Что проверить/доделать -- Синхронизация Firebase config между `index.html` и `firebase-messaging-sw.js`. -- Работа iOS Safari (PWA через Home Screen). -- Поведение при смене token. -- Надёжность `FcmPushSender` (таймауты, логирование ошибок ответа). - ---- - -## 3) Вторая слева вкладка: Личные сообщения / Чаты - -## 3.1 Продуктовая логика (как должно быть) -1. Открытие списка диалогов на вкладке "Личные сообщения". -2. Кнопка `+` открывает поиск пользователя по префиксу логина (`SearchUsers`). -3. **Первое сообщение можно отправить любому пользователю**, даже если он не контакт. -4. Если пользователь не в контактах — в чате показывается действие "Добавить в контакты". -5. Входящее сообщение может прийти: - - напрямую по WS (`IncomingDirectMessage`) - - через FCM (fallback) -6. На клиенте дедупликация должна быть по `messageId`. - -## 3.2 Что уже сделано технически -### Сервер -- `SendDirectMessage` -- `AckIncomingMessage` -- `direct_messages` таблица + DAO -- доставка по активным WS-сессиям + ожидание ACK + fallback в FCM - -### Клиент -- `authService.sendDirectMessage(...)` -- `authService.ackIncomingMessage(...)` -- WS client поддерживает server events (`onEvent`) -- В `app.js` обработка `IncomingDirectMessage` -- В `state.js` добавлены `incomingDedup`, `addIncomingMessage` - -## 3.3 Что глючит/что проверить -- Стабильность отображения новых чатов, если сообщение пришло от неизвестного пользователя. -- Согласованность списка диалогов и фактических сообщений. -- Поведение при быстрых дубликатах WS + FCM. -- Поведение при сетевых обрывах/повторном коннекте. - ---- - -## 4) Центральная вкладка: Связи / Близкие друзья - -## 4.1 Целевое поведение -1. Экран "Связи" загружает граф друзей (`GetUserConnectionsGraph`). -2. Кнопка "Добавить близкого друга" открывает модалку: - - поле логина/префикса - - кнопка "Поиск" - - кнопка "Назад" -3. Поиск идёт через `SearchUsers`. -4. По клику на найденного — подтверждение "Добавить? Да/Нет". -5. При "Да" вызывается `AddCloseFriend`, после успеха: - - закрыть модалку - - вернуться на экран связей - - обновить граф. - -## 4.2 Что уже сделано -- UI-flow модалки реализован на `network-view.js`. -- API `AddCloseFriend` добавлен на сервере. -- `ConnectionsStateDAO.upsertRelation(...)` добавлен для upsert связи FRIEND. - -## 4.3 Что проверить/доделать -- Валидация edge-cases (добавление самого себя, несуществующий логин). -- Корректность отрисовки графа после добавления. -- Согласованность данных с будущей "настоящей" записью в blockchain (сейчас MVP upsert в `connections_state`). - ---- - -## 5) SessionRevoked и мультисессии - -## 5.1 Целевое поведение -Если с другой сессии закрыли текущую сессию: -1. сервер шлёт событие `SessionRevoked` -2. клиент чистит локальную авторизацию -3. клиент возвращается в состояние неавторизованного входа. - -## 5.2 Что проверить -- Что событие приходит до закрытия socket. -- Что UX не "зависает" на промежуточном экране. - ---- - -## 6) API-операции (быстрый список) - -### Уже используемые/добавленные в ветке -- `SearchUsers` -- `ListContacts` -- `GetUserConnectionsGraph` -- `AddCloseFriend` -- `UpsertPushToken` -- `SendDirectMessage` -- `AckIncomingMessage` -- `CloseActiveSession` (с server event `SessionRevoked`) - ---- - -## 7) Минимальный чек-лист тестирования для нового исполнителя - -1. Авторизация пользователя A и B в разных браузерах/устройствах. -2. A отправляет первое сообщение B (без контактов) — должно уйти. -3. B получает по WS, отправляется ACK, fallback FCM не должен дублировать. -4. Выключить WS у B и проверить fallback FCM. -5. Вкладка "Связи": добавить близкого друга через поиск, проверить обновление графа. -6. Закрыть сессию B с другого устройства и проверить `SessionRevoked` UX. -7. Перезапуск клиента: проверка повторной регистрации push-токена только при изменении. - ---- - -## 8) Важное ограничение текущей реализации - -Некоторые части реализованы как MVP и требуют стабилизации: -- местами UI/состояние могут рассинхронизироваться; -- fallback WS->FCM может требовать донастройки таймингов и ретраев; -- `AddCloseFriend` сейчас пишет прямое состояние связи (upsert), а не полный blockchain-поток создания блоков. - -Это ожидаемо для handoff: задача следующего исполнителя — довести до production-стабильности. - ---- - -## 9) Тестовые логины для быстрой проверки - -Для проверки работы системы можно использовать специальные тестовые аккаунты -после выполнения теста `Seed_TestDataPopulation` (он создаёт их через API): - -- `A1` -- `A2` -- `A3` -- `A4` -- `A5` -- `A6` -- `A7` -- `A8` -- `A9` -- `A10` - -Общий пароль для этих аккаунтов: - -- `1` diff --git a/build.gradle b/build.gradle index ba04be6..b1863df 100644 --- a/build.gradle +++ b/build.gradle @@ -203,6 +203,7 @@ tasks.register('startLocal', Exec) { description = "Builds server, starts local WS server and local HTTP UI for end-to-end local testing" dependsOn shadowJar + dependsOn cleanServerLogs workingDir = rootDir def wsPort = System.getProperty("localWsPort", "7070") diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler.java index 09bc0f3..0c21007 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler.java @@ -309,6 +309,7 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { // Нормализация: -1 не пишем в БД (для совместимости со старым TextBody) if (prevLineNumber != null && prevLineNumber == -1) { + lineCode = null; prevLineNumber = null; prevLineHash32 = null; thisLineNumber = null; diff --git a/task/2.md b/task/2.md deleted file mode 100644 index 716a1c3..0000000 --- a/task/2.md +++ /dev/null @@ -1,31 +0,0 @@ -# Задача 2 — Проверка работы личных данных и статусов профиля (правая вкладка) - -## Что реализовано -- На правой вкладке `Профиль` отображаются реальные пользовательские параметры, загружаемые через `ListUserParams`. -- Поля профиля: - - `first_name` (чтение с обратной совместимостью с `name`) - - `last_name` - - `address_physical` - - `address_web` - - `phone` -- Кнопка `Обновить` открывает форму редактирования и сохраняет изменения в пользовательские параметры блокчейна. -- Добавлены рабочие переключатели: - - `official` - - `shine` -- Для `official`/`shine` используется подтверждение перед записью, с предупреждением, что изменение идёт через блокчейн-параметры и требует подписи ключом пользователя. -- Если `official`/`shine` отсутствуют в параметрах, они считаются `no` по умолчанию. - -## Что проверить вручную -1. Авторизоваться и открыть правую вкладку `Профиль`. -2. Убедиться, что поля профиля читаются из `ListUserParams`, а не из заглушек. -3. Нажать `Обновить`, изменить `first_name/last_name/address_physical/address_web/phone`, нажать `Сохранить`. -4. Убедиться, что после сохранения данные перечитались и обновились на экране. -5. Нажать `Официальный`, подтвердить изменение и проверить смену `no -> yes` (или `yes -> no`). -6. Нажать `Сияющий`, подтвердить изменение и проверить смену `no -> yes` (или `yes -> no`). -7. Обновить страницу и убедиться, что состояния `official/shine` и личные поля сохраняются. -8. Проверить кейс отсутствия `official/shine` в истории: UI должен показывать `no`. - -## Ожидаемый результат -- Правая вкладка профиля работает с реальными данными пользователя. -- `official` и `shine` работают как настоящие параметры (yes/no), а не заглушки. -- После каждой записи UI делает повторный `ListUserParams` и показывает актуальное состояние.