Start server-side channel read RPC handlers and simplify API spec
This commit is contained in:
parent
eb5593c7be
commit
9723696b2c
208
Dev_Docs/API/06_Channels_Read_API.md
Normal file
208
Dev_Docs/API/06_Channels_Read_API.md
Normal file
@ -0,0 +1,208 @@
|
||||
# 06. Channels Read API
|
||||
|
||||
## Человеко-читаемое объяснение
|
||||
Эти 3 функции — это **чтение данных каналов** для UI:
|
||||
|
||||
1. `ListSubscriptionsFeed` — отдает данные для экрана списка каналов:
|
||||
- ваши каналы (личный + созданные вами),
|
||||
- каналы пользователей, на кого вы подписаны,
|
||||
- отдельные каналы, на которые вы подписаны напрямую.
|
||||
|
||||
2. `GetChannelMessages` — отдает полную ленту одного канала (пока без курсоров, загружается сразу целиком),
|
||||
включая версии сообщений, лайки и ответы.
|
||||
|
||||
3. `GetMessageThread` — отдает дерево обсуждения вокруг конкретного сообщения:
|
||||
предки, фокус-сообщение, потомки.
|
||||
|
||||
> На первом этапе мы **не используем курсоры** (`nextCursor`) и загружаем полные списки.
|
||||
|
||||
---
|
||||
|
||||
## 1) ListSubscriptionsFeed
|
||||
|
||||
### Request
|
||||
```json
|
||||
{
|
||||
"op": "ListSubscriptionsFeed",
|
||||
"requestId": "req-1",
|
||||
"payload": {
|
||||
"login": "Alice",
|
||||
"limit": 200
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response (success)
|
||||
```json
|
||||
{
|
||||
"op": "ListSubscriptionsFeed",
|
||||
"requestId": "req-1",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"login": "Alice",
|
||||
"ownedChannels": [
|
||||
{
|
||||
"channel": {
|
||||
"ownerLogin": "Alice",
|
||||
"ownerBlockchainName": "alice-001",
|
||||
"channelName": "0",
|
||||
"personal": true,
|
||||
"channelRoot": { "blockNumber": 0, "blockHash": "..." }
|
||||
},
|
||||
"messagesCount": 120,
|
||||
"lastMessage": {
|
||||
"messageRef": { "blockNumber": 921, "blockHash": "..." },
|
||||
"text": "последняя версия текста",
|
||||
"createdAtMs": 1760000000000,
|
||||
"authorLogin": "Alice",
|
||||
"authorBlockchainName": "alice-001"
|
||||
}
|
||||
}
|
||||
],
|
||||
"followedUsersChannels": [
|
||||
{
|
||||
"channel": {
|
||||
"ownerLogin": "Bob",
|
||||
"ownerBlockchainName": "bob-001",
|
||||
"channelName": "0",
|
||||
"personal": true,
|
||||
"channelRoot": { "blockNumber": 0, "blockHash": "..." }
|
||||
},
|
||||
"messagesCount": 540,
|
||||
"lastMessage": {
|
||||
"messageRef": { "blockNumber": 922, "blockHash": "..." },
|
||||
"text": "последняя версия текста",
|
||||
"createdAtMs": 1760000100000,
|
||||
"authorLogin": "Bob",
|
||||
"authorBlockchainName": "bob-001"
|
||||
}
|
||||
}
|
||||
],
|
||||
"followedChannels": [
|
||||
{
|
||||
"channel": {
|
||||
"ownerLogin": "Carl",
|
||||
"ownerBlockchainName": "carl-001",
|
||||
"channelName": "market",
|
||||
"personal": false,
|
||||
"channelRoot": { "blockNumber": 456, "blockHash": "..." }
|
||||
},
|
||||
"messagesCount": 90,
|
||||
"lastMessage": {
|
||||
"messageRef": { "blockNumber": 1002, "blockHash": "..." },
|
||||
"text": "актуальный текст",
|
||||
"createdAtMs": 1760001000000,
|
||||
"authorLogin": "Carl",
|
||||
"authorBlockchainName": "carl-001"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2) GetChannelMessages
|
||||
|
||||
### Request
|
||||
```json
|
||||
{
|
||||
"op": "GetChannelMessages",
|
||||
"requestId": "req-2",
|
||||
"payload": {
|
||||
"channel": {
|
||||
"ownerBlockchainName": "bob-001",
|
||||
"channelRootBlockNumber": 123,
|
||||
"channelRootBlockHash": "..."
|
||||
},
|
||||
"limit": 200,
|
||||
"sort": "asc"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response (success)
|
||||
```json
|
||||
{
|
||||
"op": "GetChannelMessages",
|
||||
"requestId": "req-2",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"channel": {
|
||||
"ownerLogin": "Bob",
|
||||
"ownerBlockchainName": "bob-001",
|
||||
"channelName": "news",
|
||||
"channelRoot": { "blockNumber": 123, "blockHash": "..." }
|
||||
},
|
||||
"messages": [
|
||||
{
|
||||
"messageRef": { "blockNumber": 140, "blockHash": "..." },
|
||||
"authorLogin": "Bob",
|
||||
"authorBlockchainName": "bob-001",
|
||||
"createdAtMs": 1760000000000,
|
||||
"text": "текущая версия",
|
||||
"likesCount": 12,
|
||||
"repliesCount": 3,
|
||||
"versionsTotal": 4,
|
||||
"versions": [
|
||||
{ "versionIndex": 1, "blockNumber": 140, "blockHash": "...", "text": "v1", "createdAtMs": 1760000000000 },
|
||||
{ "versionIndex": 2, "blockNumber": 155, "blockHash": "...", "text": "v2", "createdAtMs": 1760001000000 },
|
||||
{ "versionIndex": 3, "blockNumber": 170, "blockHash": "...", "text": "v3", "createdAtMs": 1760002000000 },
|
||||
{ "versionIndex": 4, "blockNumber": 199, "blockHash": "...", "text": "v4", "createdAtMs": 1760003000000 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3) GetMessageThread
|
||||
|
||||
### Request
|
||||
```json
|
||||
{
|
||||
"op": "GetMessageThread",
|
||||
"requestId": "req-3",
|
||||
"payload": {
|
||||
"message": {
|
||||
"blockchainName": "bob-001",
|
||||
"blockNumber": 333,
|
||||
"blockHash": "..."
|
||||
},
|
||||
"depthUp": 20,
|
||||
"depthDown": 2,
|
||||
"limitChildrenPerNode": 50
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response (success)
|
||||
```json
|
||||
{
|
||||
"op": "GetMessageThread",
|
||||
"requestId": "req-3",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"ancestors": [MessageNode],
|
||||
"focus": MessageNode,
|
||||
"descendants": [MessageNodeTree]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reason codes
|
||||
- `bad_fields`
|
||||
- `user_not_found`
|
||||
- `channel_not_found`
|
||||
- `message_not_found`
|
||||
- `limit_too_large`
|
||||
- `channel_name_already_exists`
|
||||
- `internal_error`
|
||||
@ -39,6 +39,7 @@ import * as contactSearchView from './pages/contact-search-view.js?v=20260330001
|
||||
import * as chatView from './pages/chat-view.js?v=20260330001044';
|
||||
import * as channelsList from './pages/channels-list.js?v=20260330001044';
|
||||
import * as channelView from './pages/channel-view.js?v=20260330001044';
|
||||
import * as addChannelView from './pages/add-channel-view.js?v=20260330001044';
|
||||
import * as networkView from './pages/network-view.js?v=20260330001044';
|
||||
import * as notificationsView from './pages/notifications-view.js?v=20260330001044';
|
||||
|
||||
@ -69,6 +70,7 @@ const routes = {
|
||||
'chat-view': chatView,
|
||||
'channels-list': channelsList,
|
||||
'channel-view': channelView,
|
||||
'add-channel-view': addChannelView,
|
||||
'network-view': networkView,
|
||||
'notifications-view': notificationsView,
|
||||
};
|
||||
|
||||
@ -147,35 +147,80 @@ export const chatMessages = {
|
||||
|
||||
export const channels = [
|
||||
{
|
||||
id: 'ch1',
|
||||
name: 'Новости продукта',
|
||||
initials: 'НП',
|
||||
description: 'Официальный канал команды Shine с релизами и обновлениями.',
|
||||
lastMessage: 'Опубликовали обзор нового демо-прототипа мобильного интерфейса.',
|
||||
id: 'ch0',
|
||||
name: 'Личный канал',
|
||||
initials: 'ЛК',
|
||||
ownerLogin: '@shine.alex',
|
||||
ownerName: 'Вы',
|
||||
description: 'Ваш основной канал (нулевой).',
|
||||
lastMessage: 'Добро пожаловать в личный канал.',
|
||||
time: '16:05',
|
||||
unread: 14,
|
||||
messagesCount: 14,
|
||||
kind: 'own-personal',
|
||||
},
|
||||
{
|
||||
id: 'ch1',
|
||||
name: 'Команда продукта',
|
||||
initials: 'КП',
|
||||
ownerLogin: '@shine.alex',
|
||||
ownerName: 'Вы',
|
||||
description: 'Канал команды, который вы создали.',
|
||||
lastMessage: 'Обновили roadmap на апрель.',
|
||||
time: '15:42',
|
||||
messagesCount: 8,
|
||||
kind: 'own',
|
||||
},
|
||||
{
|
||||
id: 'ch2',
|
||||
name: 'Анекдоты дня',
|
||||
initials: 'АД',
|
||||
description: 'Лёгкий развлекательный канал с короткими шутками и мемами.',
|
||||
lastMessage: 'Новый пост: как дизайнер, разработчик и дедлайн зашли в бар.',
|
||||
name: 'Новости Bob',
|
||||
initials: 'NB',
|
||||
ownerLogin: '@bob',
|
||||
ownerName: 'Bob',
|
||||
description: 'Основной канал пользователя Bob.',
|
||||
lastMessage: 'Вышел новый дайджест разработчика.',
|
||||
time: '15:20',
|
||||
unread: 3,
|
||||
messagesCount: 5,
|
||||
kind: 'followed-user-channel',
|
||||
},
|
||||
{
|
||||
id: 'ch3',
|
||||
name: 'Новости рынка',
|
||||
initials: 'НР',
|
||||
description: 'Короткие ежедневные сводки по рынку, технологиям и сообществам.',
|
||||
lastMessage: 'В ленте свежая подборка новостей и главных событий дня.',
|
||||
name: 'Стендап команды Bob',
|
||||
initials: 'SB',
|
||||
ownerLogin: '@bob',
|
||||
ownerName: 'Bob',
|
||||
description: 'Второй канал пользователя Bob.',
|
||||
lastMessage: 'Перенесли созвон на 19:30.',
|
||||
time: 'вчера',
|
||||
unread: 0,
|
||||
messagesCount: 11,
|
||||
kind: 'followed-user-channel',
|
||||
},
|
||||
{
|
||||
id: 'ch4',
|
||||
name: 'Анекдоты дня',
|
||||
initials: 'АД',
|
||||
ownerLogin: '@fun.club',
|
||||
ownerName: 'Fun Club',
|
||||
description: 'Публичный развлекательный канал по подписке.',
|
||||
lastMessage: 'Сегодня в выпуске 5 новых шуток.',
|
||||
time: 'вчера',
|
||||
messagesCount: 33,
|
||||
kind: 'subscribed',
|
||||
},
|
||||
];
|
||||
|
||||
export const channelPosts = {
|
||||
ch0: [
|
||||
{
|
||||
id: 'p0-1',
|
||||
title: 'Первый личный пост',
|
||||
body: 'Этот канал всегда ваш и стоит в списке первым.',
|
||||
},
|
||||
{
|
||||
id: 'p0-2',
|
||||
title: 'Планы',
|
||||
body: 'Сюда удобно сохранять личные заметки и объявления.',
|
||||
},
|
||||
],
|
||||
ch1: [
|
||||
{
|
||||
id: 'p1',
|
||||
|
||||
38
shine-UI/js/pages/add-channel-view.js
Normal file
38
shine-UI/js/pages/add-channel-view.js
Normal file
@ -0,0 +1,38 @@
|
||||
import { renderHeader } from '../components/header.js?v=20260330001044';
|
||||
|
||||
export const pageMeta = { id: 'add-channel-view', title: 'Добавить канал' };
|
||||
|
||||
export function render({ navigate }) {
|
||||
const screen = document.createElement('section');
|
||||
screen.className = 'stack';
|
||||
|
||||
screen.append(
|
||||
renderHeader({
|
||||
title: 'Добавить канал',
|
||||
leftAction: { label: '←', onClick: () => navigate('channels-list') },
|
||||
})
|
||||
);
|
||||
|
||||
const form = document.createElement('form');
|
||||
form.className = 'card stack';
|
||||
form.innerHTML = `
|
||||
<label for="channel-name">Имя канала</label>
|
||||
<input id="channel-name" class="input" maxlength="64" placeholder="Например: Новости команды" required />
|
||||
<div style="display:grid; grid-template-columns:1fr 1fr; gap:10px;">
|
||||
<button type="button" class="secondary-btn" id="cancel-create-channel">Отмена</button>
|
||||
<button type="submit" class="primary-btn">Создать</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
form.addEventListener('submit', (event) => {
|
||||
event.preventDefault();
|
||||
navigate('channels-list');
|
||||
});
|
||||
|
||||
form.querySelector('#cancel-create-channel').addEventListener('click', () => {
|
||||
navigate('channels-list');
|
||||
});
|
||||
|
||||
screen.append(form);
|
||||
return screen;
|
||||
}
|
||||
@ -7,6 +7,7 @@ export function render({ navigate, route }) {
|
||||
const channelId = route.params.channelId || 'ch1';
|
||||
const channel = channels.find((c) => c.id === channelId) || channels[0];
|
||||
const posts = channelPosts[channelId] || [];
|
||||
const isOwnChannel = channel.ownerLogin === '@shine.alex';
|
||||
|
||||
const screen = document.createElement('section');
|
||||
screen.className = 'stack';
|
||||
@ -23,9 +24,13 @@ export function render({ navigate, route }) {
|
||||
head.innerHTML = `
|
||||
<strong># ${channel.name}</strong>
|
||||
<p class="meta-muted" style="margin-top:4px;">${channel.description}</p>
|
||||
<p class="meta-muted" style="margin-top:8px;">Публичный канал, режим только чтение</p>
|
||||
<p class="meta-muted" style="margin-top:8px;">Владелец: ${channel.ownerName}</p>
|
||||
`;
|
||||
|
||||
const actionButton = document.createElement('button');
|
||||
actionButton.className = isOwnChannel ? 'primary-btn' : 'secondary-btn';
|
||||
actionButton.textContent = isOwnChannel ? 'Добавить сообщение в канал' : 'Отписаться от канала';
|
||||
|
||||
const feed = document.createElement('div');
|
||||
feed.className = 'stack';
|
||||
|
||||
@ -36,6 +41,6 @@ export function render({ navigate, route }) {
|
||||
feed.append(card);
|
||||
});
|
||||
|
||||
screen.append(head, feed);
|
||||
screen.append(head, actionButton, feed);
|
||||
return screen;
|
||||
}
|
||||
|
||||
@ -3,21 +3,31 @@ import { channels } from '../mock-data.js?v=20260330001044';
|
||||
|
||||
export const pageMeta = { id: 'channels-list', title: 'Каналы' };
|
||||
|
||||
export function render({ navigate }) {
|
||||
const screen = document.createElement('section');
|
||||
screen.className = 'stack';
|
||||
function openSimpleSubscribeModal(kindLabel) {
|
||||
const root = document.getElementById('modal-root');
|
||||
root.innerHTML = `
|
||||
<div class="modal" id="channels-subscribe-modal">
|
||||
<div class="modal-card stack">
|
||||
<h3 style="font-size:18px;">${kindLabel}</h3>
|
||||
<label class="meta-muted" for="subscribe-input">Введите идентификатор</label>
|
||||
<input id="subscribe-input" class="input" placeholder="@login или #канал" />
|
||||
<div style="display:grid; grid-template-columns:1fr 1fr; gap:10px;">
|
||||
<button class="secondary-btn" id="sub-cancel">Отмена</button>
|
||||
<button class="primary-btn" id="sub-submit">Подписаться</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
screen.append(renderHeader({ title: 'Каналы' }));
|
||||
const close = () => {
|
||||
root.innerHTML = '';
|
||||
};
|
||||
|
||||
const search = document.createElement('div');
|
||||
search.className = 'card';
|
||||
search.textContent = 'Найти канал';
|
||||
search.style.color = 'var(--text-muted)';
|
||||
root.querySelector('#sub-cancel').addEventListener('click', close);
|
||||
root.querySelector('#sub-submit').addEventListener('click', close);
|
||||
}
|
||||
|
||||
const list = document.createElement('div');
|
||||
list.className = 'stack';
|
||||
|
||||
channels.forEach((channel) => {
|
||||
function renderChannelRow(channel, navigate) {
|
||||
const row = document.createElement('article');
|
||||
row.className = 'list-item';
|
||||
row.innerHTML = `
|
||||
@ -26,17 +36,91 @@ export function render({ navigate }) {
|
||||
<strong># ${channel.name}</strong>
|
||||
<p class="meta-muted" style="margin-top:4px;">${channel.description}</p>
|
||||
<p class="meta-muted" style="margin-top:6px; color:#d8e3ff;">${channel.lastMessage}</p>
|
||||
<p class="meta-muted" style="margin-top:6px;">Владелец: ${channel.ownerName}</p>
|
||||
</div>
|
||||
<div style="display:grid; justify-items:end; gap:6px;">
|
||||
<span class="badge alt" style="padding:4px 8px; font-size:10px;">Канал</span>
|
||||
<span class="meta-muted">${channel.time}</span>
|
||||
${channel.unread ? `<span class="unread">${channel.unread}</span>` : '<span></span>'}
|
||||
<span class="unread">${channel.messagesCount}</span>
|
||||
</div>
|
||||
`;
|
||||
row.addEventListener('click', () => navigate(`channel-view/${channel.id}`));
|
||||
list.append(row);
|
||||
return row;
|
||||
}
|
||||
|
||||
function renderSection(title, items, navigate) {
|
||||
const wrap = document.createElement('section');
|
||||
wrap.className = 'stack';
|
||||
|
||||
const header = document.createElement('h3');
|
||||
header.className = 'section-title';
|
||||
header.textContent = title;
|
||||
|
||||
wrap.append(header);
|
||||
|
||||
items.forEach((channel) => {
|
||||
wrap.append(renderChannelRow(channel, navigate));
|
||||
});
|
||||
|
||||
screen.append(search, list);
|
||||
return wrap;
|
||||
}
|
||||
|
||||
export function render({ navigate }) {
|
||||
const screen = document.createElement('section');
|
||||
screen.className = 'stack';
|
||||
|
||||
screen.append(
|
||||
renderHeader({
|
||||
title: 'Каналы',
|
||||
rightActions: [
|
||||
{ label: 'Подписаться на человека', onClick: () => openSimpleSubscribeModal('Подписка на человека') },
|
||||
{ label: 'Подписаться на канал', onClick: () => openSimpleSubscribeModal('Подписка на канал') },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
const ownChannels = channels
|
||||
.filter((channel) => channel.kind === 'own-personal' || channel.kind === 'own')
|
||||
.sort((a, b) => {
|
||||
if (a.kind === 'own-personal') return -1;
|
||||
if (b.kind === 'own-personal') return 1;
|
||||
return a.name.localeCompare(b.name, 'ru');
|
||||
});
|
||||
|
||||
const followedUserChannels = channels.filter((channel) => channel.kind === 'followed-user-channel');
|
||||
const subscribedChannels = channels.filter((channel) => channel.kind === 'subscribed');
|
||||
|
||||
const listWrap = document.createElement('div');
|
||||
listWrap.className = 'channels-scroll-wrap';
|
||||
|
||||
const list = document.createElement('div');
|
||||
list.className = 'stack channels-groups';
|
||||
|
||||
list.append(renderSection('Мои каналы', ownChannels, navigate));
|
||||
|
||||
const dividerOne = document.createElement('hr');
|
||||
dividerOne.className = 'channels-divider';
|
||||
list.append(dividerOne);
|
||||
|
||||
list.append(renderSection('Каналы пользователей, на кого вы подписаны', followedUserChannels, navigate));
|
||||
|
||||
const dividerTwo = document.createElement('hr');
|
||||
dividerTwo.className = 'channels-divider';
|
||||
list.append(dividerTwo);
|
||||
|
||||
list.append(renderSection('Каналы, на которые вы подписаны', subscribedChannels, navigate));
|
||||
|
||||
const addChannelButton = document.createElement('button');
|
||||
addChannelButton.className = 'primary-btn';
|
||||
addChannelButton.textContent = 'Добавить канал';
|
||||
addChannelButton.addEventListener('click', () => navigate('add-channel-view'));
|
||||
|
||||
list.append(addChannelButton);
|
||||
|
||||
const scrollHint = document.createElement('div');
|
||||
scrollHint.className = 'channels-scroll-hint';
|
||||
|
||||
listWrap.append(list, scrollHint);
|
||||
screen.append(listWrap);
|
||||
return screen;
|
||||
}
|
||||
|
||||
@ -57,6 +57,6 @@ export function resolveToolbarActive(pageId) {
|
||||
return 'profile-view';
|
||||
}
|
||||
if (pageId === 'chat-view' || pageId === 'contact-search-view') return 'messages-list';
|
||||
if (pageId === 'channel-view') return 'channels-list';
|
||||
if (pageId === 'channel-view' || pageId === 'add-channel-view') return 'channels-list';
|
||||
return 'profile-view';
|
||||
}
|
||||
|
||||
@ -728,3 +728,38 @@
|
||||
border-radius: 16px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #dbe7ff;
|
||||
margin: 4px 2px;
|
||||
}
|
||||
|
||||
.channels-scroll-wrap {
|
||||
position: relative;
|
||||
max-height: 58vh;
|
||||
overflow-y: auto;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.channels-groups {
|
||||
min-height: min-content;
|
||||
}
|
||||
|
||||
.channels-divider {
|
||||
border: 0;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.14);
|
||||
margin: 6px 0 8px;
|
||||
}
|
||||
|
||||
.channels-scroll-hint {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 0;
|
||||
width: 4px;
|
||||
height: calc(100% - 8px);
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(180deg, rgba(83, 216, 251, 0.55), rgba(83, 216, 251, 0.15));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@ -45,6 +45,12 @@ import server.logic.ws_protocol.JSON.handlers.userParams.entyties.Net_UpsertUser
|
||||
// --- NEW: connections friends lists ---
|
||||
import server.logic.ws_protocol.JSON.handlers.connections.Net_GetFriendsLists_Handler;
|
||||
import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_GetFriendsLists_Request;
|
||||
import server.logic.ws_protocol.JSON.handlers.channels.Net_GetChannelMessages_Handler;
|
||||
import server.logic.ws_protocol.JSON.handlers.channels.Net_GetMessageThread_Handler;
|
||||
import server.logic.ws_protocol.JSON.handlers.channels.Net_ListSubscriptionsFeed_Handler;
|
||||
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetChannelMessages_Request;
|
||||
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetMessageThread_Request;
|
||||
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_ListSubscriptionsFeed_Request;
|
||||
|
||||
// --- NEW: Ping ---
|
||||
import server.logic.ws_protocol.JSON.handlers.system.Net_GetServerInfo_Handler;
|
||||
@ -85,6 +91,9 @@ public final class JsonHandlerRegistry {
|
||||
|
||||
// --- connections ---
|
||||
Map.entry("GetFriendsLists", new Net_GetFriendsLists_Handler()),
|
||||
Map.entry("ListSubscriptionsFeed", new Net_ListSubscriptionsFeed_Handler()),
|
||||
Map.entry("GetChannelMessages", new Net_GetChannelMessages_Handler()),
|
||||
Map.entry("GetMessageThread", new Net_GetMessageThread_Handler()),
|
||||
|
||||
// --- system ---
|
||||
Map.entry("Ping", new Net_Ping_Handler()),
|
||||
@ -119,6 +128,9 @@ public final class JsonHandlerRegistry {
|
||||
|
||||
// --- connections ---
|
||||
Map.entry("GetFriendsLists", Net_GetFriendsLists_Request.class),
|
||||
Map.entry("ListSubscriptionsFeed", Net_ListSubscriptionsFeed_Request.class),
|
||||
Map.entry("GetChannelMessages", Net_GetChannelMessages_Request.class),
|
||||
Map.entry("GetMessageThread", Net_GetMessageThread_Request.class),
|
||||
|
||||
// --- system ---
|
||||
Map.entry("Ping", Net_Ping_Request.class),
|
||||
|
||||
@ -5,6 +5,7 @@ import blockchain.BchCryptoVerifier;
|
||||
import blockchain.MsgSubType;
|
||||
import blockchain.body.BodyHasLine;
|
||||
import blockchain.body.BodyHasTarget;
|
||||
import blockchain.body.CreateChannelBody;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.Base64Ws;
|
||||
@ -25,6 +26,9 @@ import shine.db.entities.BlockEntry;
|
||||
import utils.blockchain.BlockchainNameUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
@ -128,6 +132,7 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
|
||||
case "prev_line_block_not_found" -> "Не найден блок prevLineNumber для проверки линии";
|
||||
case "bad_prev_line_hash" -> "Некорректный prevLineHash";
|
||||
case "db_error_prev_line_check" -> "Ошибка БД при проверке prevLine";
|
||||
case "channel_name_already_exists" -> "Канал с таким именем уже существует";
|
||||
case "internal_error" -> "Внутренняя ошибка сервера при записи блока";
|
||||
default -> "Ошибка: " + code;
|
||||
};
|
||||
@ -228,6 +233,18 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
|
||||
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_block_body", serverLastNum, serverLastHashHex);
|
||||
}
|
||||
|
||||
if (block.body instanceof CreateChannelBody createChannelBody) {
|
||||
try {
|
||||
if (channelNameExists(blockchainName, createChannelBody.channelName)) {
|
||||
return new AddBlockResult(409, "channel_name_already_exists", serverLastNum, serverLastHashHex);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("AddBlock: channel_name_check_failed (blockchainName={}, channelName={})",
|
||||
blockchainName, createChannelBody.channelName, e);
|
||||
return new AddBlockResult(WireCodes.Status.INTERNAL_ERROR, "internal_error", serverLastNum, serverLastHashHex);
|
||||
}
|
||||
}
|
||||
|
||||
// 4.2) запрет дырок: blockNumber строго last+1
|
||||
int expectedBlockNumber = serverLastNum + 1;
|
||||
if (block.blockNumber != expectedBlockNumber) {
|
||||
@ -378,6 +395,32 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
|
||||
return Base64Ws.decode(b64);
|
||||
}
|
||||
|
||||
private boolean channelNameExists(String blockchainName, String channelName) throws Exception {
|
||||
String sql = """
|
||||
SELECT block_bytes
|
||||
FROM blocks
|
||||
WHERE bch_name = ? AND msg_type = 0 AND msg_sub_type = 1
|
||||
""";
|
||||
try (Connection c = shine.db.SqliteDbController.getInstance().getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, blockchainName);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
byte[] bytes = rs.getBytes("block_bytes");
|
||||
try {
|
||||
BchBlockEntry entry = new BchBlockEntry(bytes);
|
||||
if (entry.body instanceof CreateChannelBody ccb) {
|
||||
if (ccb.channelName.equalsIgnoreCase(channelName)) return true;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// ignore bad historic rows, uniqueness check is best effort
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static long safeAdd(long a, long b) {
|
||||
long r = a + b;
|
||||
if (((a ^ r) & (b ^ r)) < 0) throw new ArithmeticException("long overflow");
|
||||
|
||||
@ -0,0 +1,240 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.channels;
|
||||
|
||||
import blockchain.BchBlockEntry;
|
||||
import blockchain.body.BodyRecord;
|
||||
import blockchain.body.CreateChannelBody;
|
||||
import blockchain.body.TextBody;
|
||||
import shine.db.MsgSubType;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
final class ChannelsReadSupport {
|
||||
static final int MSG_TYPE_TEXT = 1;
|
||||
static final int MSG_TYPE_TECH = 0;
|
||||
|
||||
private ChannelsReadSupport() {}
|
||||
|
||||
static String canonicalLogin(Connection c, String anyCaseLogin) throws SQLException {
|
||||
String sql = "SELECT login FROM solana_users WHERE login = ? COLLATE NOCASE LIMIT 1";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, anyCaseLogin);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
return rs.next() ? rs.getString("login") : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String detectChannelName(Connection c, String ownerBch, int rootNumber) throws SQLException {
|
||||
if (rootNumber == 0) return "0";
|
||||
|
||||
String sql = "SELECT block_bytes FROM blocks WHERE bch_name=? AND block_number=? LIMIT 1";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, ownerBch);
|
||||
ps.setInt(2, rootNumber);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) return null;
|
||||
byte[] bytes = rs.getBytes("block_bytes");
|
||||
BchBlockEntry e = new BchBlockEntry(bytes);
|
||||
BodyRecord body = e.body;
|
||||
if (body instanceof CreateChannelBody ccb) return ccb.channelName;
|
||||
return null;
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int countPosts(Connection c, String ownerBch, int lineCode) throws SQLException {
|
||||
String sql = "SELECT COUNT(*) AS cnt FROM blocks WHERE bch_name=? AND msg_type=? AND msg_sub_type=? AND line_code=?";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, ownerBch);
|
||||
ps.setInt(2, MSG_TYPE_TEXT);
|
||||
ps.setInt(3, MsgSubType.TEXT_POST);
|
||||
ps.setInt(4, lineCode);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
return rs.next() ? rs.getInt("cnt") : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static PostBlock loadLastPost(Connection c, String ownerBch, int lineCode) throws SQLException {
|
||||
String sql = """
|
||||
SELECT login,bch_name,block_number,block_hash,block_bytes
|
||||
FROM blocks
|
||||
WHERE bch_name=? AND msg_type=? AND msg_sub_type=? AND line_code=?
|
||||
ORDER BY block_number DESC
|
||||
LIMIT 1
|
||||
""";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, ownerBch);
|
||||
ps.setInt(2, MSG_TYPE_TEXT);
|
||||
ps.setInt(3, MsgSubType.TEXT_POST);
|
||||
ps.setInt(4, lineCode);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) return null;
|
||||
PostBlock pb = new PostBlock();
|
||||
pb.login = rs.getString("login");
|
||||
pb.bchName = rs.getString("bch_name");
|
||||
pb.blockNumber = rs.getInt("block_number");
|
||||
pb.blockHash = rs.getBytes("block_hash");
|
||||
pb.blockBytes = rs.getBytes("block_bytes");
|
||||
return pb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static PostBlock loadLastVersion(Connection c, String ownerBch, int originalBlockNumber, byte[] originalHash) throws SQLException {
|
||||
String sql = """
|
||||
SELECT login,bch_name,block_number,block_hash,block_bytes
|
||||
FROM blocks
|
||||
WHERE bch_name=? AND msg_type=? AND msg_sub_type=?
|
||||
AND to_bch_name=? AND to_block_number=? AND to_block_hash=?
|
||||
ORDER BY block_number DESC
|
||||
LIMIT 1
|
||||
""";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, ownerBch);
|
||||
ps.setInt(2, MSG_TYPE_TEXT);
|
||||
ps.setInt(3, MsgSubType.TEXT_EDIT_POST);
|
||||
ps.setString(4, ownerBch);
|
||||
ps.setInt(5, originalBlockNumber);
|
||||
ps.setBytes(6, originalHash);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) return null;
|
||||
PostBlock pb = new PostBlock();
|
||||
pb.login = rs.getString("login");
|
||||
pb.bchName = rs.getString("bch_name");
|
||||
pb.blockNumber = rs.getInt("block_number");
|
||||
pb.blockHash = rs.getBytes("block_hash");
|
||||
pb.blockBytes = rs.getBytes("block_bytes");
|
||||
return pb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static TextInfo parseTextAndTime(byte[] blockBytes) {
|
||||
try {
|
||||
BchBlockEntry e = new BchBlockEntry(blockBytes);
|
||||
TextInfo ti = new TextInfo();
|
||||
ti.createdAtMs = e.timeMs;
|
||||
if (e.body instanceof TextBody tb) {
|
||||
ti.text = tb.message;
|
||||
}
|
||||
return ti;
|
||||
} catch (Exception ex) {
|
||||
return new TextInfo();
|
||||
}
|
||||
}
|
||||
|
||||
static List<PostBlock> channelPosts(Connection c, String ownerBch, int lineCode, int limit, boolean asc) throws SQLException {
|
||||
String order = asc ? "ASC" : "DESC";
|
||||
String sql = """
|
||||
SELECT login,bch_name,block_number,block_hash,block_bytes
|
||||
FROM blocks
|
||||
WHERE bch_name=? AND msg_type=? AND msg_sub_type=? AND line_code=?
|
||||
ORDER BY block_number """ + order + " LIMIT ?";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, ownerBch);
|
||||
ps.setInt(2, MSG_TYPE_TEXT);
|
||||
ps.setInt(3, MsgSubType.TEXT_POST);
|
||||
ps.setInt(4, lineCode);
|
||||
ps.setInt(5, limit);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
List<PostBlock> out = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
PostBlock pb = new PostBlock();
|
||||
pb.login = rs.getString("login");
|
||||
pb.bchName = rs.getString("bch_name");
|
||||
pb.blockNumber = rs.getInt("block_number");
|
||||
pb.blockHash = rs.getBytes("block_hash");
|
||||
pb.blockBytes = rs.getBytes("block_bytes");
|
||||
out.add(pb);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static List<PostBlock> versionsForPost(Connection c, String ownerBch, int originalBlock, byte[] originalHash) throws SQLException {
|
||||
String sql = """
|
||||
SELECT login,bch_name,block_number,block_hash,block_bytes
|
||||
FROM blocks
|
||||
WHERE bch_name=? AND msg_type=? AND msg_sub_type=?
|
||||
AND to_bch_name=? AND to_block_number=? AND to_block_hash=?
|
||||
ORDER BY block_number ASC
|
||||
""";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, ownerBch);
|
||||
ps.setInt(2, MSG_TYPE_TEXT);
|
||||
ps.setInt(3, MsgSubType.TEXT_EDIT_POST);
|
||||
ps.setString(4, ownerBch);
|
||||
ps.setInt(5, originalBlock);
|
||||
ps.setBytes(6, originalHash);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
List<PostBlock> out = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
PostBlock pb = new PostBlock();
|
||||
pb.login = rs.getString("login");
|
||||
pb.bchName = rs.getString("bch_name");
|
||||
pb.blockNumber = rs.getInt("block_number");
|
||||
pb.blockHash = rs.getBytes("block_hash");
|
||||
pb.blockBytes = rs.getBytes("block_bytes");
|
||||
out.add(pb);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int[] loadStats(Connection c, String bch, int blockNumber, byte[] blockHash) throws SQLException {
|
||||
String sql = "SELECT likes_count,replies_count FROM message_stats WHERE to_bch_name=? AND to_block_number=? AND to_block_hash=? LIMIT 1";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, bch);
|
||||
ps.setInt(2, blockNumber);
|
||||
ps.setBytes(3, blockHash);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) return new int[] {0, 0};
|
||||
return new int[] {rs.getInt("likes_count"), rs.getInt("replies_count")};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] hexToBytes(String s) {
|
||||
if (s == null) return null;
|
||||
String x = s.trim();
|
||||
if ((x.length() & 1) != 0) throw new IllegalArgumentException("hex length must be even");
|
||||
byte[] out = new byte[x.length() / 2];
|
||||
for (int i = 0; i < out.length; i++) {
|
||||
int hi = Character.digit(x.charAt(i * 2), 16);
|
||||
int lo = Character.digit(x.charAt(i * 2 + 1), 16);
|
||||
if (hi < 0 || lo < 0) throw new IllegalArgumentException("bad hex");
|
||||
out[i] = (byte) ((hi << 4) | lo);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static String toHex(byte[] bytes) {
|
||||
if (bytes == null) return null;
|
||||
StringBuilder sb = new StringBuilder(bytes.length * 2);
|
||||
for (byte b : bytes) sb.append(String.format("%02x", b));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
static final class PostBlock {
|
||||
String login;
|
||||
String bchName;
|
||||
int blockNumber;
|
||||
byte[] blockHash;
|
||||
byte[] blockBytes;
|
||||
}
|
||||
|
||||
static final class TextInfo {
|
||||
String text = "";
|
||||
long createdAtMs = 0L;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,115 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.channels;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetChannelMessages_Request;
|
||||
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetChannelMessages_Response;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.SqliteDbController;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Net_GetChannelMessages_Handler implements JsonMessageHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(Net_GetChannelMessages_Handler.class);
|
||||
|
||||
@Override
|
||||
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
|
||||
Net_GetChannelMessages_Request req = (Net_GetChannelMessages_Request) baseRequest;
|
||||
if (req.getChannel() == null
|
||||
|| req.getChannel().getOwnerBlockchainName() == null
|
||||
|| req.getChannel().getOwnerBlockchainName().isBlank()
|
||||
|| req.getChannel().getChannelRootBlockNumber() == null) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "bad_fields", "Некорректные поля channel");
|
||||
}
|
||||
|
||||
int limit = req.getLimit() == null ? 30 : req.getLimit();
|
||||
if (limit <= 0 || limit > 1000) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "limit_too_large", "Некорректный limit");
|
||||
}
|
||||
|
||||
boolean asc = req.getSort() == null || !"desc".equalsIgnoreCase(req.getSort());
|
||||
|
||||
try (Connection c = SqliteDbController.getInstance().getConnection()) {
|
||||
String ownerBch = req.getChannel().getOwnerBlockchainName();
|
||||
int lineCode = req.getChannel().getChannelRootBlockNumber();
|
||||
|
||||
Net_GetChannelMessages_Response resp = new Net_GetChannelMessages_Response();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
|
||||
Net_GetChannelMessages_Response.Channel channel = new Net_GetChannelMessages_Response.Channel();
|
||||
channel.setOwnerBlockchainName(ownerBch);
|
||||
channel.setOwnerLogin(ownerBch.contains("-") ? ownerBch.substring(0, ownerBch.indexOf('-')) : ownerBch);
|
||||
channel.setChannelName(ChannelsReadSupport.detectChannelName(c, ownerBch, lineCode));
|
||||
Net_GetChannelMessages_Response.BlockRef rootRef = new Net_GetChannelMessages_Response.BlockRef();
|
||||
rootRef.setBlockNumber(lineCode);
|
||||
rootRef.setBlockHash(req.getChannel().getChannelRootBlockHash());
|
||||
channel.setChannelRoot(rootRef);
|
||||
resp.setChannel(channel);
|
||||
|
||||
List<ChannelsReadSupport.PostBlock> posts = ChannelsReadSupport.channelPosts(c, ownerBch, lineCode, limit, asc);
|
||||
List<Net_GetChannelMessages_Response.MessageItem> items = new ArrayList<>();
|
||||
|
||||
for (ChannelsReadSupport.PostBlock post : posts) {
|
||||
Net_GetChannelMessages_Response.MessageItem item = new Net_GetChannelMessages_Response.MessageItem();
|
||||
Net_GetChannelMessages_Response.BlockRef msgRef = new Net_GetChannelMessages_Response.BlockRef();
|
||||
msgRef.setBlockNumber(post.blockNumber);
|
||||
msgRef.setBlockHash(ChannelsReadSupport.toHex(post.blockHash));
|
||||
item.setMessageRef(msgRef);
|
||||
item.setAuthorLogin(post.login);
|
||||
item.setAuthorBlockchainName(post.bchName);
|
||||
|
||||
List<Net_GetChannelMessages_Response.VersionItem> versionsOut = new ArrayList<>();
|
||||
int index = 1;
|
||||
|
||||
ChannelsReadSupport.TextInfo postText = ChannelsReadSupport.parseTextAndTime(post.blockBytes);
|
||||
Net_GetChannelMessages_Response.VersionItem v1 = new Net_GetChannelMessages_Response.VersionItem();
|
||||
v1.setVersionIndex(index++);
|
||||
v1.setBlockNumber(post.blockNumber);
|
||||
v1.setBlockHash(ChannelsReadSupport.toHex(post.blockHash));
|
||||
v1.setText(postText.text);
|
||||
v1.setCreatedAtMs(postText.createdAtMs);
|
||||
versionsOut.add(v1);
|
||||
|
||||
List<ChannelsReadSupport.PostBlock> edits = ChannelsReadSupport.versionsForPost(c, ownerBch, post.blockNumber, post.blockHash);
|
||||
for (ChannelsReadSupport.PostBlock edit : edits) {
|
||||
ChannelsReadSupport.TextInfo editText = ChannelsReadSupport.parseTextAndTime(edit.blockBytes);
|
||||
Net_GetChannelMessages_Response.VersionItem ve = new Net_GetChannelMessages_Response.VersionItem();
|
||||
ve.setVersionIndex(index++);
|
||||
ve.setBlockNumber(edit.blockNumber);
|
||||
ve.setBlockHash(ChannelsReadSupport.toHex(edit.blockHash));
|
||||
ve.setText(editText.text);
|
||||
ve.setCreatedAtMs(editText.createdAtMs);
|
||||
versionsOut.add(ve);
|
||||
}
|
||||
|
||||
item.setVersions(versionsOut);
|
||||
item.setVersionsTotal(versionsOut.size());
|
||||
|
||||
Net_GetChannelMessages_Response.VersionItem lastV = versionsOut.get(versionsOut.size() - 1);
|
||||
item.setText(lastV.getText());
|
||||
item.setCreatedAtMs(postText.createdAtMs);
|
||||
|
||||
int[] stats = ChannelsReadSupport.loadStats(c, ownerBch, post.blockNumber, post.blockHash);
|
||||
item.setLikesCount(stats[0]);
|
||||
item.setRepliesCount(stats[1]);
|
||||
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
resp.setMessages(items);
|
||||
return resp;
|
||||
} catch (Exception e) {
|
||||
log.error("GetChannelMessages failed", e);
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.INTERNAL_ERROR, "internal_error", "Внутренняя ошибка сервера");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,224 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.channels;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetChannelMessages_Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetMessageThread_Request;
|
||||
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetMessageThread_Response;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.MsgSubType;
|
||||
import shine.db.SqliteDbController;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Net_GetMessageThread_Handler implements JsonMessageHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(Net_GetMessageThread_Handler.class);
|
||||
|
||||
@Override
|
||||
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
|
||||
Net_GetMessageThread_Request req = (Net_GetMessageThread_Request) baseRequest;
|
||||
if (req.getMessage() == null || req.getMessage().getBlockchainName() == null || req.getMessage().getBlockNumber() == null) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "bad_fields", "Некорректные поля message");
|
||||
}
|
||||
|
||||
int depthUp = req.getDepthUp() == null ? 20 : Math.max(0, req.getDepthUp());
|
||||
int depthDown = req.getDepthDown() == null ? 2 : Math.max(0, req.getDepthDown());
|
||||
int childLimit = req.getLimitChildrenPerNode() == null ? 50 : Math.max(1, req.getLimitChildrenPerNode());
|
||||
|
||||
try (Connection c = SqliteDbController.getInstance().getConnection()) {
|
||||
PostRow focusRow = findByNumber(c, req.getMessage().getBlockchainName(), req.getMessage().getBlockNumber());
|
||||
if (focusRow == null) {
|
||||
return NetExceptionResponseFactory.error(req, 404, "message_not_found", "Сообщение не найдено");
|
||||
}
|
||||
|
||||
Net_GetMessageThread_Response resp = new Net_GetMessageThread_Response();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
|
||||
resp.setFocus(toNode(c, focusRow));
|
||||
|
||||
List<Net_GetMessageThread_Response.MessageNode> ancestors = new ArrayList<>();
|
||||
PostRow cur = focusRow;
|
||||
for (int i = 0; i < depthUp; i++) {
|
||||
if (cur.toBlockNumber == null || cur.toBchName == null) break;
|
||||
PostRow parent = findByNumber(c, cur.toBchName, cur.toBlockNumber);
|
||||
if (parent == null) break;
|
||||
ancestors.add(0, toNode(c, parent));
|
||||
cur = parent;
|
||||
}
|
||||
resp.setAncestors(ancestors);
|
||||
|
||||
resp.setDescendants(loadChildren(c, focusRow, depthDown, childLimit));
|
||||
return resp;
|
||||
} catch (Exception e) {
|
||||
log.error("GetMessageThread failed", e);
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.INTERNAL_ERROR, "internal_error", "Внутренняя ошибка сервера");
|
||||
}
|
||||
}
|
||||
|
||||
private List<Net_GetMessageThread_Response.MessageNodeTree> loadChildren(Connection c, PostRow parent, int depthDown, int childLimit) throws Exception {
|
||||
if (depthDown <= 0) return List.of();
|
||||
List<PostRow> replies = findReplies(c, parent.bchName, parent.blockNumber, parent.blockHash, childLimit);
|
||||
List<Net_GetMessageThread_Response.MessageNodeTree> out = new ArrayList<>();
|
||||
for (PostRow row : replies) {
|
||||
Net_GetMessageThread_Response.MessageNodeTree t = new Net_GetMessageThread_Response.MessageNodeTree();
|
||||
t.setNode(toNode(c, row));
|
||||
t.setChildren(loadChildren(c, row, depthDown - 1, childLimit));
|
||||
out.add(t);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private List<PostRow> findReplies(Connection c, String toBchName, int toBlockNumber, byte[] toBlockHash, int limit) throws Exception {
|
||||
String sql = """
|
||||
SELECT login,bch_name,block_number,block_hash,block_bytes,to_bch_name,to_block_number,to_block_hash,line_code,msg_sub_type
|
||||
FROM blocks
|
||||
WHERE msg_type=1 AND msg_sub_type=?
|
||||
AND to_bch_name=? AND to_block_number=? AND to_block_hash=?
|
||||
ORDER BY block_number ASC
|
||||
LIMIT ?
|
||||
""";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setInt(1, MsgSubType.TEXT_REPLY);
|
||||
ps.setString(2, toBchName);
|
||||
ps.setInt(3, toBlockNumber);
|
||||
ps.setBytes(4, toBlockHash);
|
||||
ps.setInt(5, limit);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
List<PostRow> out = new ArrayList<>();
|
||||
while (rs.next()) out.add(mapRow(rs));
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PostRow findByNumber(Connection c, String bchName, int blockNumber) throws Exception {
|
||||
String sql = """
|
||||
SELECT login,bch_name,block_number,block_hash,block_bytes,to_bch_name,to_block_number,to_block_hash,line_code,msg_sub_type
|
||||
FROM blocks
|
||||
WHERE bch_name=? AND block_number=?
|
||||
LIMIT 1
|
||||
""";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, bchName);
|
||||
ps.setInt(2, blockNumber);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
return rs.next() ? mapRow(rs) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PostRow mapRow(ResultSet rs) throws Exception {
|
||||
PostRow row = new PostRow();
|
||||
row.login = rs.getString("login");
|
||||
row.bchName = rs.getString("bch_name");
|
||||
row.blockNumber = rs.getInt("block_number");
|
||||
row.blockHash = rs.getBytes("block_hash");
|
||||
row.blockBytes = rs.getBytes("block_bytes");
|
||||
row.toBchName = rs.getString("to_bch_name");
|
||||
row.toBlockNumber = (Integer) rs.getObject("to_block_number");
|
||||
row.toBlockHash = rs.getBytes("to_block_hash");
|
||||
row.lineCode = (Integer) rs.getObject("line_code");
|
||||
row.msgSubType = rs.getInt("msg_sub_type");
|
||||
return row;
|
||||
}
|
||||
|
||||
private Net_GetMessageThread_Response.MessageNode toNode(Connection c, PostRow row) throws Exception {
|
||||
Net_GetMessageThread_Response.MessageNode node = new Net_GetMessageThread_Response.MessageNode();
|
||||
Net_GetChannelMessages_Response.BlockRef ref = new Net_GetChannelMessages_Response.BlockRef();
|
||||
ref.setBlockNumber(row.blockNumber);
|
||||
ref.setBlockHash(ChannelsReadSupport.toHex(row.blockHash));
|
||||
node.setMessageRef(ref);
|
||||
node.setAuthorLogin(row.login);
|
||||
node.setAuthorBlockchainName(row.bchName);
|
||||
|
||||
ChannelsReadSupport.TextInfo base = ChannelsReadSupport.parseTextAndTime(row.blockBytes);
|
||||
node.setCreatedAtMs(base.createdAtMs);
|
||||
|
||||
List<Net_GetChannelMessages_Response.VersionItem> versions = new ArrayList<>();
|
||||
Net_GetChannelMessages_Response.VersionItem first = new Net_GetChannelMessages_Response.VersionItem();
|
||||
first.setVersionIndex(1);
|
||||
first.setBlockNumber(row.blockNumber);
|
||||
first.setBlockHash(ChannelsReadSupport.toHex(row.blockHash));
|
||||
first.setText(base.text);
|
||||
first.setCreatedAtMs(base.createdAtMs);
|
||||
versions.add(first);
|
||||
|
||||
short editType = row.msgSubType == MsgSubType.TEXT_REPLY ? MsgSubType.TEXT_EDIT_REPLY : MsgSubType.TEXT_EDIT_POST;
|
||||
for (PostRow edit : findEdits(c, row.bchName, row.blockNumber, row.blockHash, editType)) {
|
||||
ChannelsReadSupport.TextInfo et = ChannelsReadSupport.parseTextAndTime(edit.blockBytes);
|
||||
Net_GetChannelMessages_Response.VersionItem v = new Net_GetChannelMessages_Response.VersionItem();
|
||||
v.setVersionIndex(versions.size() + 1);
|
||||
v.setBlockNumber(edit.blockNumber);
|
||||
v.setBlockHash(ChannelsReadSupport.toHex(edit.blockHash));
|
||||
v.setText(et.text);
|
||||
v.setCreatedAtMs(et.createdAtMs);
|
||||
versions.add(v);
|
||||
}
|
||||
|
||||
node.setVersions(versions);
|
||||
node.setVersionsTotal(versions.size());
|
||||
node.setText(versions.get(versions.size() - 1).getText());
|
||||
|
||||
int[] stats = ChannelsReadSupport.loadStats(c, row.bchName, row.blockNumber, row.blockHash);
|
||||
node.setLikesCount(stats[0]);
|
||||
node.setRepliesCount(stats[1]);
|
||||
|
||||
if (row.lineCode != null && row.lineCode >= 0) {
|
||||
Net_GetMessageThread_Response.ChannelInfo ci = new Net_GetMessageThread_Response.ChannelInfo();
|
||||
ci.setOwnerBlockchainName(row.bchName);
|
||||
Net_GetChannelMessages_Response.BlockRef root = new Net_GetChannelMessages_Response.BlockRef();
|
||||
root.setBlockNumber(row.lineCode);
|
||||
root.setBlockHash(null);
|
||||
ci.setChannelRoot(root);
|
||||
node.setChannelInfo(ci);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private List<PostRow> findEdits(Connection c, String bch, int targetBlock, byte[] targetHash, int subType) throws Exception {
|
||||
String sql = """
|
||||
SELECT login,bch_name,block_number,block_hash,block_bytes,to_bch_name,to_block_number,to_block_hash,line_code,msg_sub_type
|
||||
FROM blocks
|
||||
WHERE bch_name=? AND msg_type=1 AND msg_sub_type=?
|
||||
AND to_bch_name=? AND to_block_number=? AND to_block_hash=?
|
||||
ORDER BY block_number ASC
|
||||
""";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, bch);
|
||||
ps.setInt(2, subType);
|
||||
ps.setString(3, bch);
|
||||
ps.setInt(4, targetBlock);
|
||||
ps.setBytes(5, targetHash);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
List<PostRow> out = new ArrayList<>();
|
||||
while (rs.next()) out.add(mapRow(rs));
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PostRow {
|
||||
String login;
|
||||
String bchName;
|
||||
int blockNumber;
|
||||
byte[] blockHash;
|
||||
byte[] blockBytes;
|
||||
String toBchName;
|
||||
Integer toBlockNumber;
|
||||
byte[] toBlockHash;
|
||||
Integer lineCode;
|
||||
int msgSubType;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,168 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.channels;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_ListSubscriptionsFeed_Request;
|
||||
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_ListSubscriptionsFeed_Response;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.MsgSubType;
|
||||
import shine.db.SqliteDbController;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Net_ListSubscriptionsFeed_Handler implements JsonMessageHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(Net_ListSubscriptionsFeed_Handler.class);
|
||||
|
||||
@Override
|
||||
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
|
||||
Net_ListSubscriptionsFeed_Request req = (Net_ListSubscriptionsFeed_Request) baseRequest;
|
||||
if (req.getLogin() == null || req.getLogin().isBlank()) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "bad_fields", "Некорректные поля: login");
|
||||
}
|
||||
|
||||
try (Connection c = SqliteDbController.getInstance().getConnection()) {
|
||||
String canonicalLogin = ChannelsReadSupport.canonicalLogin(c, req.getLogin().trim());
|
||||
if (canonicalLogin == null) {
|
||||
return NetExceptionResponseFactory.error(req, 404, "user_not_found", "Пользователь не найден");
|
||||
}
|
||||
|
||||
Net_ListSubscriptionsFeed_Response resp = new Net_ListSubscriptionsFeed_Response();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
resp.setLogin(canonicalLogin);
|
||||
|
||||
List<ChannelKey> own = loadOwnChannels(c, canonicalLogin);
|
||||
List<ChannelKey> followedUsersChannels = loadFollowedChannels(c, canonicalLogin, true);
|
||||
List<ChannelKey> followedChannels = loadFollowedChannels(c, canonicalLogin, false);
|
||||
|
||||
resp.setOwnedChannels(buildSummaries(c, own));
|
||||
resp.setFollowedUsersChannels(buildSummaries(c, followedUsersChannels));
|
||||
resp.setFollowedChannels(buildSummaries(c, followedChannels));
|
||||
|
||||
return resp;
|
||||
} catch (Exception e) {
|
||||
log.error("ListSubscriptionsFeed failed", e);
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.INTERNAL_ERROR, "internal_error", "Внутренняя ошибка сервера");
|
||||
}
|
||||
}
|
||||
|
||||
private List<Net_ListSubscriptionsFeed_Response.ChannelSummary> buildSummaries(Connection c, List<ChannelKey> keys) throws Exception {
|
||||
List<Net_ListSubscriptionsFeed_Response.ChannelSummary> out = new ArrayList<>();
|
||||
for (ChannelKey key : keys) {
|
||||
Net_ListSubscriptionsFeed_Response.ChannelSummary row = new Net_ListSubscriptionsFeed_Response.ChannelSummary();
|
||||
Net_ListSubscriptionsFeed_Response.ChannelRef channelRef = new Net_ListSubscriptionsFeed_Response.ChannelRef();
|
||||
channelRef.setOwnerLogin(key.ownerLogin);
|
||||
channelRef.setOwnerBlockchainName(key.ownerBch);
|
||||
channelRef.setChannelName(ChannelsReadSupport.detectChannelName(c, key.ownerBch, key.rootNumber));
|
||||
channelRef.setPersonal(key.rootNumber == 0);
|
||||
|
||||
Net_ListSubscriptionsFeed_Response.BlockRef rootRef = new Net_ListSubscriptionsFeed_Response.BlockRef();
|
||||
rootRef.setBlockNumber(key.rootNumber);
|
||||
rootRef.setBlockHash(ChannelsReadSupport.toHex(key.rootHash));
|
||||
channelRef.setChannelRoot(rootRef);
|
||||
|
||||
row.setChannel(channelRef);
|
||||
row.setMessagesCount(ChannelsReadSupport.countPosts(c, key.ownerBch, key.rootNumber));
|
||||
|
||||
ChannelsReadSupport.PostBlock lastPost = ChannelsReadSupport.loadLastPost(c, key.ownerBch, key.rootNumber);
|
||||
if (lastPost != null) {
|
||||
ChannelsReadSupport.PostBlock actual = ChannelsReadSupport.loadLastVersion(c, key.ownerBch, lastPost.blockNumber, lastPost.blockHash);
|
||||
if (actual == null) actual = lastPost;
|
||||
|
||||
ChannelsReadSupport.TextInfo textInfo = ChannelsReadSupport.parseTextAndTime(actual.blockBytes);
|
||||
Net_ListSubscriptionsFeed_Response.LastMessage lm = new Net_ListSubscriptionsFeed_Response.LastMessage();
|
||||
Net_ListSubscriptionsFeed_Response.BlockRef msgRef = new Net_ListSubscriptionsFeed_Response.BlockRef();
|
||||
msgRef.setBlockNumber(actual.blockNumber);
|
||||
msgRef.setBlockHash(ChannelsReadSupport.toHex(actual.blockHash));
|
||||
lm.setMessageRef(msgRef);
|
||||
lm.setText(textInfo.text);
|
||||
lm.setCreatedAtMs(textInfo.createdAtMs);
|
||||
lm.setAuthorLogin(actual.login);
|
||||
lm.setAuthorBlockchainName(actual.bchName);
|
||||
row.setLastMessage(lm);
|
||||
}
|
||||
|
||||
out.add(row);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private List<ChannelKey> loadOwnChannels(Connection c, String canonicalLogin) throws Exception {
|
||||
List<ChannelKey> out = new ArrayList<>();
|
||||
String bchSql = "SELECT blockchain_name FROM blockchain_state WHERE login=? ORDER BY blockchain_name";
|
||||
try (PreparedStatement bchPs = c.prepareStatement(bchSql)) {
|
||||
bchPs.setString(1, canonicalLogin);
|
||||
try (ResultSet bchRs = bchPs.executeQuery()) {
|
||||
while (bchRs.next()) {
|
||||
String bch = bchRs.getString("blockchain_name");
|
||||
out.add(new ChannelKey(canonicalLogin, bch, 0, new byte[32]));
|
||||
|
||||
String chSql = "SELECT block_number,block_hash FROM blocks WHERE bch_name=? AND msg_type=? AND msg_sub_type=? ORDER BY block_number";
|
||||
try (PreparedStatement chPs = c.prepareStatement(chSql)) {
|
||||
chPs.setString(1, bch);
|
||||
chPs.setInt(2, ChannelsReadSupport.MSG_TYPE_TECH);
|
||||
chPs.setInt(3, 1);
|
||||
try (ResultSet chRs = chPs.executeQuery()) {
|
||||
while (chRs.next()) {
|
||||
out.add(new ChannelKey(canonicalLogin, bch, chRs.getInt("block_number"), chRs.getBytes("block_hash")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private List<ChannelKey> loadFollowedChannels(Connection c, String canonicalLogin, boolean onlyUserRoots) throws Exception {
|
||||
List<ChannelKey> out = new ArrayList<>();
|
||||
String sql = """
|
||||
SELECT cs.to_login, cs.to_bch_name, COALESCE(cs.to_block_number,0) AS root_number, cs.to_block_hash
|
||||
FROM connections_state cs
|
||||
WHERE cs.login=? AND cs.rel_type=?
|
||||
ORDER BY cs.to_login, cs.to_bch_name, root_number
|
||||
""";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, canonicalLogin);
|
||||
ps.setInt(2, MsgSubType.CONNECTION_FOLLOW);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
int rootNumber = rs.getInt("root_number");
|
||||
if (onlyUserRoots && rootNumber != 0) continue;
|
||||
if (!onlyUserRoots && rootNumber == 0) continue;
|
||||
out.add(new ChannelKey(
|
||||
rs.getString("to_login"),
|
||||
rs.getString("to_bch_name"),
|
||||
rootNumber,
|
||||
rs.getBytes("to_block_hash")
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static final class ChannelKey {
|
||||
final String ownerLogin;
|
||||
final String ownerBch;
|
||||
final int rootNumber;
|
||||
final byte[] rootHash;
|
||||
|
||||
private ChannelKey(String ownerLogin, String ownerBch, int rootNumber, byte[] rootHash) {
|
||||
this.ownerLogin = ownerLogin;
|
||||
this.ownerBch = ownerBch;
|
||||
this.rootNumber = rootNumber;
|
||||
this.rootHash = rootHash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.channels.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
|
||||
public class Net_GetChannelMessages_Request extends Net_Request {
|
||||
private ChannelSelector channel;
|
||||
private Integer limit;
|
||||
private String sort;
|
||||
|
||||
public ChannelSelector getChannel() { return channel; }
|
||||
public void setChannel(ChannelSelector channel) { this.channel = channel; }
|
||||
|
||||
public Integer getLimit() { return limit; }
|
||||
public void setLimit(Integer limit) { this.limit = limit; }
|
||||
|
||||
public String getSort() { return sort; }
|
||||
public void setSort(String sort) { this.sort = sort; }
|
||||
|
||||
public static class ChannelSelector {
|
||||
private String ownerBlockchainName;
|
||||
private Integer channelRootBlockNumber;
|
||||
private String channelRootBlockHash;
|
||||
|
||||
public String getOwnerBlockchainName() { return ownerBlockchainName; }
|
||||
public void setOwnerBlockchainName(String ownerBlockchainName) { this.ownerBlockchainName = ownerBlockchainName; }
|
||||
|
||||
public Integer getChannelRootBlockNumber() { return channelRootBlockNumber; }
|
||||
public void setChannelRootBlockNumber(Integer channelRootBlockNumber) { this.channelRootBlockNumber = channelRootBlockNumber; }
|
||||
|
||||
public String getChannelRootBlockHash() { return channelRootBlockHash; }
|
||||
public void setChannelRootBlockHash(String channelRootBlockHash) { this.channelRootBlockHash = channelRootBlockHash; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.channels.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Net_GetChannelMessages_Response extends Net_Response {
|
||||
private Channel channel;
|
||||
private List<MessageItem> messages = new ArrayList<>();
|
||||
|
||||
public Channel getChannel() { return channel; }
|
||||
public void setChannel(Channel channel) { this.channel = channel; }
|
||||
|
||||
public List<MessageItem> getMessages() { return messages; }
|
||||
public void setMessages(List<MessageItem> messages) { this.messages = messages; }
|
||||
|
||||
public static class Channel {
|
||||
private String ownerLogin;
|
||||
private String ownerBlockchainName;
|
||||
private String channelName;
|
||||
private BlockRef channelRoot;
|
||||
|
||||
public String getOwnerLogin() { return ownerLogin; }
|
||||
public void setOwnerLogin(String ownerLogin) { this.ownerLogin = ownerLogin; }
|
||||
|
||||
public String getOwnerBlockchainName() { return ownerBlockchainName; }
|
||||
public void setOwnerBlockchainName(String ownerBlockchainName) { this.ownerBlockchainName = ownerBlockchainName; }
|
||||
|
||||
public String getChannelName() { return channelName; }
|
||||
public void setChannelName(String channelName) { this.channelName = channelName; }
|
||||
|
||||
public BlockRef getChannelRoot() { return channelRoot; }
|
||||
public void setChannelRoot(BlockRef channelRoot) { this.channelRoot = channelRoot; }
|
||||
}
|
||||
|
||||
public static class MessageItem {
|
||||
private BlockRef messageRef;
|
||||
private String authorLogin;
|
||||
private String authorBlockchainName;
|
||||
private long createdAtMs;
|
||||
private String text;
|
||||
private int likesCount;
|
||||
private int repliesCount;
|
||||
private int versionsTotal;
|
||||
private List<VersionItem> versions = new ArrayList<>();
|
||||
|
||||
public BlockRef getMessageRef() { return messageRef; }
|
||||
public void setMessageRef(BlockRef messageRef) { this.messageRef = messageRef; }
|
||||
|
||||
public String getAuthorLogin() { return authorLogin; }
|
||||
public void setAuthorLogin(String authorLogin) { this.authorLogin = authorLogin; }
|
||||
|
||||
public String getAuthorBlockchainName() { return authorBlockchainName; }
|
||||
public void setAuthorBlockchainName(String authorBlockchainName) { this.authorBlockchainName = authorBlockchainName; }
|
||||
|
||||
public long getCreatedAtMs() { return createdAtMs; }
|
||||
public void setCreatedAtMs(long createdAtMs) { this.createdAtMs = createdAtMs; }
|
||||
|
||||
public String getText() { return text; }
|
||||
public void setText(String text) { this.text = text; }
|
||||
|
||||
public int getLikesCount() { return likesCount; }
|
||||
public void setLikesCount(int likesCount) { this.likesCount = likesCount; }
|
||||
|
||||
public int getRepliesCount() { return repliesCount; }
|
||||
public void setRepliesCount(int repliesCount) { this.repliesCount = repliesCount; }
|
||||
|
||||
public int getVersionsTotal() { return versionsTotal; }
|
||||
public void setVersionsTotal(int versionsTotal) { this.versionsTotal = versionsTotal; }
|
||||
|
||||
public List<VersionItem> getVersions() { return versions; }
|
||||
public void setVersions(List<VersionItem> versions) { this.versions = versions; }
|
||||
}
|
||||
|
||||
public static class VersionItem {
|
||||
private int versionIndex;
|
||||
private int blockNumber;
|
||||
private String blockHash;
|
||||
private String text;
|
||||
private long createdAtMs;
|
||||
|
||||
public int getVersionIndex() { return versionIndex; }
|
||||
public void setVersionIndex(int versionIndex) { this.versionIndex = versionIndex; }
|
||||
|
||||
public int getBlockNumber() { return blockNumber; }
|
||||
public void setBlockNumber(int blockNumber) { this.blockNumber = blockNumber; }
|
||||
|
||||
public String getBlockHash() { return blockHash; }
|
||||
public void setBlockHash(String blockHash) { this.blockHash = blockHash; }
|
||||
|
||||
public String getText() { return text; }
|
||||
public void setText(String text) { this.text = text; }
|
||||
|
||||
public long getCreatedAtMs() { return createdAtMs; }
|
||||
public void setCreatedAtMs(long createdAtMs) { this.createdAtMs = createdAtMs; }
|
||||
}
|
||||
|
||||
public static class BlockRef {
|
||||
private int blockNumber;
|
||||
private String blockHash;
|
||||
|
||||
public int getBlockNumber() { return blockNumber; }
|
||||
public void setBlockNumber(int blockNumber) { this.blockNumber = blockNumber; }
|
||||
|
||||
public String getBlockHash() { return blockHash; }
|
||||
public void setBlockHash(String blockHash) { this.blockHash = blockHash; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.channels.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
|
||||
public class Net_GetMessageThread_Request extends Net_Request {
|
||||
private MessageSelector message;
|
||||
private Integer depthUp;
|
||||
private Integer depthDown;
|
||||
private Integer limitChildrenPerNode;
|
||||
|
||||
public MessageSelector getMessage() { return message; }
|
||||
public void setMessage(MessageSelector message) { this.message = message; }
|
||||
|
||||
public Integer getDepthUp() { return depthUp; }
|
||||
public void setDepthUp(Integer depthUp) { this.depthUp = depthUp; }
|
||||
|
||||
public Integer getDepthDown() { return depthDown; }
|
||||
public void setDepthDown(Integer depthDown) { this.depthDown = depthDown; }
|
||||
|
||||
public Integer getLimitChildrenPerNode() { return limitChildrenPerNode; }
|
||||
public void setLimitChildrenPerNode(Integer limitChildrenPerNode) { this.limitChildrenPerNode = limitChildrenPerNode; }
|
||||
|
||||
public static class MessageSelector {
|
||||
private String blockchainName;
|
||||
private Integer blockNumber;
|
||||
private String blockHash;
|
||||
|
||||
public String getBlockchainName() { return blockchainName; }
|
||||
public void setBlockchainName(String blockchainName) { this.blockchainName = blockchainName; }
|
||||
|
||||
public Integer getBlockNumber() { return blockNumber; }
|
||||
public void setBlockNumber(Integer blockNumber) { this.blockNumber = blockNumber; }
|
||||
|
||||
public String getBlockHash() { return blockHash; }
|
||||
public void setBlockHash(String blockHash) { this.blockHash = blockHash; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.channels.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Net_GetMessageThread_Response extends Net_Response {
|
||||
private List<MessageNode> ancestors = new ArrayList<>();
|
||||
private MessageNode focus;
|
||||
private List<MessageNodeTree> descendants = new ArrayList<>();
|
||||
|
||||
public List<MessageNode> getAncestors() { return ancestors; }
|
||||
public void setAncestors(List<MessageNode> ancestors) { this.ancestors = ancestors; }
|
||||
|
||||
public MessageNode getFocus() { return focus; }
|
||||
public void setFocus(MessageNode focus) { this.focus = focus; }
|
||||
|
||||
public List<MessageNodeTree> getDescendants() { return descendants; }
|
||||
public void setDescendants(List<MessageNodeTree> descendants) { this.descendants = descendants; }
|
||||
|
||||
public static class MessageNode extends Net_GetChannelMessages_Response.MessageItem {
|
||||
private ChannelInfo channelInfo;
|
||||
|
||||
public ChannelInfo getChannelInfo() { return channelInfo; }
|
||||
public void setChannelInfo(ChannelInfo channelInfo) { this.channelInfo = channelInfo; }
|
||||
}
|
||||
|
||||
public static class ChannelInfo {
|
||||
private String ownerBlockchainName;
|
||||
private Net_GetChannelMessages_Response.BlockRef channelRoot;
|
||||
|
||||
public String getOwnerBlockchainName() { return ownerBlockchainName; }
|
||||
public void setOwnerBlockchainName(String ownerBlockchainName) { this.ownerBlockchainName = ownerBlockchainName; }
|
||||
|
||||
public Net_GetChannelMessages_Response.BlockRef getChannelRoot() { return channelRoot; }
|
||||
public void setChannelRoot(Net_GetChannelMessages_Response.BlockRef channelRoot) { this.channelRoot = channelRoot; }
|
||||
}
|
||||
|
||||
public static class MessageNodeTree {
|
||||
private MessageNode node;
|
||||
private List<MessageNodeTree> children = new ArrayList<>();
|
||||
|
||||
public MessageNode getNode() { return node; }
|
||||
public void setNode(MessageNode node) { this.node = node; }
|
||||
|
||||
public List<MessageNodeTree> getChildren() { return children; }
|
||||
public void setChildren(List<MessageNodeTree> children) { this.children = children; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.channels.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
|
||||
public class Net_ListSubscriptionsFeed_Request extends Net_Request {
|
||||
private String login;
|
||||
private Integer limit;
|
||||
|
||||
public String getLogin() { return login; }
|
||||
public void setLogin(String login) { this.login = login; }
|
||||
|
||||
public Integer getLimit() { return limit; }
|
||||
public void setLimit(Integer limit) { this.limit = limit; }
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.channels.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Net_ListSubscriptionsFeed_Response extends Net_Response {
|
||||
private String login;
|
||||
private List<ChannelSummary> ownedChannels = new ArrayList<>();
|
||||
private List<ChannelSummary> followedUsersChannels = new ArrayList<>();
|
||||
private List<ChannelSummary> followedChannels = new ArrayList<>();
|
||||
|
||||
public String getLogin() { return login; }
|
||||
public void setLogin(String login) { this.login = login; }
|
||||
|
||||
public List<ChannelSummary> getOwnedChannels() { return ownedChannels; }
|
||||
public void setOwnedChannels(List<ChannelSummary> ownedChannels) { this.ownedChannels = ownedChannels; }
|
||||
|
||||
public List<ChannelSummary> getFollowedUsersChannels() { return followedUsersChannels; }
|
||||
public void setFollowedUsersChannels(List<ChannelSummary> followedUsersChannels) { this.followedUsersChannels = followedUsersChannels; }
|
||||
|
||||
public List<ChannelSummary> getFollowedChannels() { return followedChannels; }
|
||||
public void setFollowedChannels(List<ChannelSummary> followedChannels) { this.followedChannels = followedChannels; }
|
||||
|
||||
public static class ChannelSummary {
|
||||
private ChannelRef channel;
|
||||
private int messagesCount;
|
||||
private LastMessage lastMessage;
|
||||
|
||||
public ChannelRef getChannel() { return channel; }
|
||||
public void setChannel(ChannelRef channel) { this.channel = channel; }
|
||||
|
||||
public int getMessagesCount() { return messagesCount; }
|
||||
public void setMessagesCount(int messagesCount) { this.messagesCount = messagesCount; }
|
||||
|
||||
public LastMessage getLastMessage() { return lastMessage; }
|
||||
public void setLastMessage(LastMessage lastMessage) { this.lastMessage = lastMessage; }
|
||||
}
|
||||
|
||||
public static class ChannelRef {
|
||||
private String ownerLogin;
|
||||
private String ownerBlockchainName;
|
||||
private String channelName;
|
||||
private boolean personal;
|
||||
private BlockRef channelRoot;
|
||||
|
||||
public String getOwnerLogin() { return ownerLogin; }
|
||||
public void setOwnerLogin(String ownerLogin) { this.ownerLogin = ownerLogin; }
|
||||
|
||||
public String getOwnerBlockchainName() { return ownerBlockchainName; }
|
||||
public void setOwnerBlockchainName(String ownerBlockchainName) { this.ownerBlockchainName = ownerBlockchainName; }
|
||||
|
||||
public String getChannelName() { return channelName; }
|
||||
public void setChannelName(String channelName) { this.channelName = channelName; }
|
||||
|
||||
public boolean isPersonal() { return personal; }
|
||||
public void setPersonal(boolean personal) { this.personal = personal; }
|
||||
|
||||
public BlockRef getChannelRoot() { return channelRoot; }
|
||||
public void setChannelRoot(BlockRef channelRoot) { this.channelRoot = channelRoot; }
|
||||
}
|
||||
|
||||
public static class LastMessage {
|
||||
private BlockRef messageRef;
|
||||
private String text;
|
||||
private long createdAtMs;
|
||||
private String authorLogin;
|
||||
private String authorBlockchainName;
|
||||
|
||||
public BlockRef getMessageRef() { return messageRef; }
|
||||
public void setMessageRef(BlockRef messageRef) { this.messageRef = messageRef; }
|
||||
|
||||
public String getText() { return text; }
|
||||
public void setText(String text) { this.text = text; }
|
||||
|
||||
public long getCreatedAtMs() { return createdAtMs; }
|
||||
public void setCreatedAtMs(long createdAtMs) { this.createdAtMs = createdAtMs; }
|
||||
|
||||
public String getAuthorLogin() { return authorLogin; }
|
||||
public void setAuthorLogin(String authorLogin) { this.authorLogin = authorLogin; }
|
||||
|
||||
public String getAuthorBlockchainName() { return authorBlockchainName; }
|
||||
public void setAuthorBlockchainName(String authorBlockchainName) { this.authorBlockchainName = authorBlockchainName; }
|
||||
}
|
||||
|
||||
public static class BlockRef {
|
||||
private int blockNumber;
|
||||
private String blockHash;
|
||||
|
||||
public int getBlockNumber() { return blockNumber; }
|
||||
public void setBlockNumber(int blockNumber) { this.blockNumber = blockNumber; }
|
||||
|
||||
public String getBlockHash() { return blockHash; }
|
||||
public void setBlockHash(String blockHash) { this.blockHash = blockHash; }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user