Каналы: типы 0/1/100/200, CreateChannel v3, state для chat200, новые API и деплой на prod

This commit is contained in:
AidarKC
2026-05-13 01:17:23 +03:00
parent 97d37a2eb6
commit e95f65ac78
36 changed files with 1764 additions and 187 deletions
@@ -48,9 +48,15 @@ import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_GetFriend
import server.logic.ws_protocol.JSON.handlers.channels.ChannelNamesStateBootstrapper;
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_GetGroupDialog_Handler;
import server.logic.ws_protocol.JSON.handlers.channels.Net_GetChannelsCounters_Handler;
import server.logic.ws_protocol.JSON.handlers.channels.Net_ListGroupChats200_Handler;
import server.logic.ws_protocol.JSON.handlers.channels.Net_ListSubscriptionsFeed_Handler;
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetChannelsCounters_Request;
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetChannelMessages_Request;
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetGroupDialog_Request;
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetMessageThread_Request;
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_ListGroupChats200_Request;
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_ListSubscriptionsFeed_Request;
import server.logic.ws_protocol.JSON.handlers.connections.Net_GetUserConnectionsGraph_Handler;
import server.logic.ws_protocol.JSON.handlers.connections.Net_AddCloseFriend_Handler;
@@ -129,6 +135,9 @@ public final class JsonHandlerRegistry {
Map.entry("ListSubscriptionsFeed", new Net_ListSubscriptionsFeed_Handler()),
Map.entry("GetChannelMessages", new Net_GetChannelMessages_Handler()),
Map.entry("GetMessageThread", new Net_GetMessageThread_Handler()),
Map.entry("GetGroupDialog", new Net_GetGroupDialog_Handler()),
Map.entry("ListGroupChats200", new Net_ListGroupChats200_Handler()),
Map.entry("GetChannelsCounters", new Net_GetChannelsCounters_Handler()),
Map.entry("ListContacts", new Net_ListContacts_Handler()),
Map.entry("GetUserConnectionsGraph", new Net_GetUserConnectionsGraph_Handler()),
Map.entry("AddCloseFriend", new Net_AddCloseFriend_Handler()),
@@ -184,6 +193,9 @@ public final class JsonHandlerRegistry {
Map.entry("ListSubscriptionsFeed", Net_ListSubscriptionsFeed_Request.class),
Map.entry("GetChannelMessages", Net_GetChannelMessages_Request.class),
Map.entry("GetMessageThread", Net_GetMessageThread_Request.class),
Map.entry("GetGroupDialog", Net_GetGroupDialog_Request.class),
Map.entry("ListGroupChats200", Net_ListGroupChats200_Request.class),
Map.entry("GetChannelsCounters", Net_GetChannelsCounters_Request.class),
Map.entry("ListContacts", Net_ListContacts_Request.class),
Map.entry("GetUserConnectionsGraph", Net_GetUserConnectionsGraph_Request.class),
Map.entry("AddCloseFriend", Net_AddCloseFriend_Request.class),
@@ -6,6 +6,7 @@ import blockchain.MsgSubType;
import blockchain.body.BodyHasLine;
import blockchain.body.BodyHasTarget;
import blockchain.body.CreateChannelBody;
import blockchain.body.TextLineBody;
import blockchain.body.UserParamBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,6 +35,8 @@ 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;
/**
@@ -142,7 +145,6 @@ 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_zero_writes_disabled" -> "Запись в канал 0 временно отключена";
case "channel_name_already_exists" -> "Такое название канала уже занято";
case "internal_error" -> "Внутренняя ошибка сервера при записи блока";
default -> "Ошибка: " + code;
@@ -245,6 +247,7 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
}
ChannelNameStateEntry channelNameStateEntry = null;
Chat200CreateSeed chat200CreateSeed = null;
if (block.body instanceof CreateChannelBody createChannelBody) {
final String normalizedName;
final String slug;
@@ -255,8 +258,11 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_channel_name", serverLastNum, serverLastHashHex);
}
int channelTypeCode = Short.toUnsignedInt(createChannelBody.channelTypeCode);
int channelTypeVersion = Short.toUnsignedInt(createChannelBody.channelTypeVersion);
try {
if (channelNameStateDAO.existsBySlug(slug)) {
if (channelNameStateDAO.existsByOwnerTypeAndSlug(blockchainName, channelTypeCode, slug)) {
return new AddBlockResult(409, "channel_name_already_exists", serverLastNum, serverLastHashHex);
}
} catch (Exception e) {
@@ -275,9 +281,23 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
);
channelNameStateEntry.setOwnerLogin(login);
channelNameStateEntry.setOwnerBlockchainName(blockchainName);
channelNameStateEntry.setChannelTypeCode(channelTypeCode);
channelNameStateEntry.setChannelTypeVersion(channelTypeVersion);
channelNameStateEntry.setChannelRootBlockNumber(block.blockNumber);
channelNameStateEntry.setChannelRootBlockHash(block.getHash32());
channelNameStateEntry.setCreatedAtMs(block.timestamp * 1000L);
if (channelTypeCode == (CreateChannelBody.CHANNEL_TYPE_GROUP & 0xFFFF)) {
chat200CreateSeed = new Chat200CreateSeed();
chat200CreateSeed.ownerLogin = login;
chat200CreateSeed.ownerBch = blockchainName;
chat200CreateSeed.rootBlockNumber = block.blockNumber;
chat200CreateSeed.rootBlockHash = block.getHash32();
chat200CreateSeed.channelName = normalizedName;
chat200CreateSeed.channelTypeVersion = channelTypeVersion;
chat200CreateSeed.chatTitle = channelNameStateEntry.getChannelDescription();
chat200CreateSeed.updatedAtMs = block.timestamp * 1000L;
}
}
// 4.2) запрет дырок: blockNumber строго last+1
@@ -338,17 +358,6 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
prevLineHash32 = bl.prevLineBlockHash32();
thisLineNumber = bl.lineSeq();
// Канал 0 сохраняем как технический root, но публикации в него пока не принимаем.
// Это правило защищает от "случайных" постов в дефолтный канал.
int msgType = block.type & 0xFFFF;
int msgSubType = block.subType & 0xFFFF;
if (msgType == 1
&& msgSubType == (MsgSubType.TEXT_POST & 0xFFFF)
&& lineCode != null
&& lineCode == 0) {
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "channel_zero_writes_disabled", serverLastNum, serverLastHashHex);
}
// Нормализация: -1 не пишем в БД (для совместимости со старым TextBody)
if (prevLineNumber != null && prevLineNumber == -1) {
lineCode = null;
@@ -433,6 +442,11 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
}
dbWriter.appendBlockAndState(blockchainName, block, st, be, upsertedParam, channelNameStateEntry);
if (chat200CreateSeed != null) {
upsertChat200StateFromCreate(chat200CreateSeed);
}
maybeApplyChat200Command(blockchainName, block);
} catch (Exception e) {
if (isChannelSlugConflict(e)) {
return new AddBlockResult(409, "channel_name_already_exists", serverLastNum, serverLastHashHex);
@@ -463,8 +477,9 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
Throwable cur = throwable;
while (cur != null) {
String message = String.valueOf(cur.getMessage());
if (message.contains("channel_names_state.slug")
|| message.contains("uq_channel_names_state_slug")) {
if (message.contains("uq_channel_names_state_owner_type_slug")
|| message.contains("channel_names_state.owner_bch_name")
|| message.contains("channel_names_state.slug")) {
return true;
}
cur = cur.getCause();
@@ -472,6 +487,149 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
return false;
}
private static final class Chat200CreateSeed {
String ownerLogin;
String ownerBch;
int rootBlockNumber;
byte[] rootBlockHash;
String channelName;
int channelTypeVersion;
String chatTitle;
long updatedAtMs;
}
private void upsertChat200StateFromCreate(Chat200CreateSeed seed) throws Exception {
try (Connection c = shine.db.SqliteDbController.getInstance().getConnection();
PreparedStatement ps = c.prepareStatement("""
INSERT INTO chat200_state (
owner_login, owner_bch_name, channel_root_block_number, channel_root_block_hash,
channel_name, channel_type_version, chat_title, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(owner_bch_name, channel_root_block_number) DO UPDATE SET
owner_login = excluded.owner_login,
channel_root_block_hash = excluded.channel_root_block_hash,
channel_name = excluded.channel_name,
channel_type_version = excluded.channel_type_version,
chat_title = excluded.chat_title,
updated_at_ms = excluded.updated_at_ms
""")) {
ps.setString(1, seed.ownerLogin);
ps.setString(2, seed.ownerBch);
ps.setInt(3, seed.rootBlockNumber);
ps.setBytes(4, seed.rootBlockHash);
ps.setString(5, seed.channelName);
ps.setInt(6, seed.channelTypeVersion);
ps.setString(7, seed.chatTitle == null ? "" : seed.chatTitle);
ps.setLong(8, seed.updatedAtMs);
ps.executeUpdate();
}
}
private void maybeApplyChat200Command(String ownerBch, BchBlockEntry block) throws Exception {
int msgType = block.type & 0xFFFF;
int msgSubType = block.subType & 0xFFFF;
if (msgType != 1 || msgSubType != (MsgSubType.TEXT_POST & 0xFFFF)) return;
if (!(block.body instanceof TextLineBody tlb)) return;
Integer lineCode = tlb.lineCode();
if (lineCode == null || lineCode <= 0) return;
if (!isChat200Channel(ownerBch, lineCode)) return;
CommandParsed cmd = parseChatCommand(tlb.message);
if (cmd == null) return;
long updatedAtMs = block.timestamp * 1000L;
if ("desc".equals(cmd.command)) {
try (Connection c = shine.db.SqliteDbController.getInstance().getConnection();
PreparedStatement ps = c.prepareStatement("""
UPDATE chat200_state
SET chat_title = ?, updated_at_ms = ?
WHERE owner_bch_name = ? AND channel_root_block_number = ?
""")) {
ps.setString(1, cmd.arg1 == null ? "" : cmd.arg1);
ps.setLong(2, updatedAtMs);
ps.setString(3, ownerBch);
ps.setInt(4, lineCode);
ps.executeUpdate();
}
return;
}
if (!("add".equals(cmd.command) || "remove".equals(cmd.command))) return;
String memberLogin = cmd.arg1 == null ? "" : cmd.arg1.trim();
String memberChannel = cmd.arg2 == null ? "" : cmd.arg2.trim();
if (memberLogin.isBlank() || memberChannel.isBlank()) return;
try (Connection c = shine.db.SqliteDbController.getInstance().getConnection();
PreparedStatement ps = c.prepareStatement("""
INSERT INTO chat200_members_state (
owner_bch_name, channel_root_block_number, member_login, member_channel_name,
is_active, updated_at_ms, updated_by_block_number
) VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(owner_bch_name, channel_root_block_number, member_login, member_channel_name)
DO UPDATE SET
is_active = excluded.is_active,
updated_at_ms = excluded.updated_at_ms,
updated_by_block_number = excluded.updated_by_block_number
""")) {
ps.setString(1, ownerBch);
ps.setInt(2, lineCode);
ps.setString(3, memberLogin);
ps.setString(4, memberChannel);
ps.setInt(5, "add".equals(cmd.command) ? 1 : 0);
ps.setLong(6, updatedAtMs);
ps.setInt(7, block.blockNumber);
ps.executeUpdate();
}
}
private boolean isChat200Channel(String ownerBch, int rootBlockNumber) throws Exception {
try (Connection c = shine.db.SqliteDbController.getInstance().getConnection();
PreparedStatement ps = c.prepareStatement("""
SELECT channel_type_code
FROM channel_names_state
WHERE owner_bch_name = ? AND channel_root_block_number = ?
LIMIT 1
""")) {
ps.setString(1, ownerBch);
ps.setInt(2, rootBlockNumber);
try (ResultSet rs = ps.executeQuery()) {
if (!rs.next()) return false;
return rs.getInt("channel_type_code") == (CreateChannelBody.CHANNEL_TYPE_GROUP & 0xFFFF);
}
}
}
private static final class CommandParsed {
final String command;
final String arg1;
final String arg2;
private CommandParsed(String command, String arg1, String arg2) {
this.command = command;
this.arg1 = arg1;
this.arg2 = arg2;
}
}
private static CommandParsed parseChatCommand(String text) {
String value = String.valueOf(text == null ? "" : text).trim();
if (!value.startsWith("/.")) return null;
String raw = value.substring(2).trim();
if (raw.isBlank()) return null;
int sp = raw.indexOf(' ');
String cmd = (sp < 0 ? raw : raw.substring(0, sp)).trim().toLowerCase();
String tail = sp < 0 ? "" : raw.substring(sp + 1).trim();
if ("desc".equals(cmd)) return new CommandParsed("desc", tail, "");
if ("add".equals(cmd) || "remove".equals(cmd)) {
String[] parts = tail.split("\\s+", 2);
String a1 = parts.length > 0 ? parts[0] : "";
String a2 = parts.length > 1 ? parts[1] : "";
return new CommandParsed(cmd, a1, a2);
}
return null;
}
private static long safeAdd(long a, long b) {
long r = a + b;
if (((a ^ r) & (b ^ r)) < 0) throw new ArithmeticException("long overflow");
@@ -77,19 +77,24 @@ public final class ChannelNamesStateBootstrapper {
final String displayName;
final String slug;
final String channelDescription;
final int channelTypeCode;
final int channelTypeVersion;
try {
displayName = ChannelNameRules.normalizeDisplayName(createChannelBody.channelName);
slug = ChannelNameRules.toCanonicalSlug(displayName);
channelDescription = ChannelNameRules.normalizeDisplayName(createChannelBody.channelDescription);
channelTypeCode = Short.toUnsignedInt(createChannelBody.channelTypeCode);
channelTypeVersion = Short.toUnsignedInt(createChannelBody.channelTypeVersion);
} catch (Exception badName) {
skipped.add(ownerBch + "#" + blockNumber + " (invalid_name)");
continue;
}
String identity = ownerBch + "#" + blockNumber;
String existing = slugToIdentity.putIfAbsent(slug, identity);
String ownerTypeSlug = ownerBch + "|" + channelTypeCode + "|" + slug;
String existing = slugToIdentity.putIfAbsent(ownerTypeSlug, identity);
if (existing != null && !existing.equals(identity)) {
conflicts.add("slug=\"" + slug + "\" conflicts: " + existing + " vs " + identity);
conflicts.add("owner/type/slug=\"" + ownerTypeSlug + "\" conflicts: " + existing + " vs " + identity);
continue;
}
@@ -99,6 +104,8 @@ public final class ChannelNamesStateBootstrapper {
entry.setChannelDescription(channelDescription == null ? "" : channelDescription);
entry.setOwnerLogin(ownerLogin);
entry.setOwnerBlockchainName(ownerBch);
entry.setChannelTypeCode(channelTypeCode);
entry.setChannelTypeVersion(channelTypeVersion);
entry.setChannelRootBlockNumber(blockNumber);
entry.setChannelRootBlockHash(blockHash);
entry.setCreatedAtMs(parsed.timestamp * 1000L);
@@ -6,6 +6,7 @@ import blockchain.body.CreateChannelBody;
import blockchain.body.TextBody;
import blockchain.body.TextLineBody;
import blockchain.body.TextReplyBody;
import shine.db.channels.ChannelNameRules;
import shine.db.MsgSubType;
import java.sql.Connection;
@@ -13,12 +14,18 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
final class ChannelsReadSupport {
static final int MSG_TYPE_TEXT = 1;
static final int MSG_TYPE_REACTION = 2;
static final int MSG_TYPE_TECH = 0;
static final String STORIES_CHANNEL_NAME = "stories";
static final String COMMAND_PREFIX = "/.";
static final String COMMAND_DESC = "desc";
static final String COMMAND_ADD = "add";
static final String COMMAND_REMOVE = "remove";
private ChannelsReadSupport() {}
@@ -32,22 +39,51 @@ final class ChannelsReadSupport {
}
}
static String detectChannelName(Connection c, String ownerBch, int rootNumber) throws SQLException {
if (rootNumber == 0) return "news";
static ChannelMeta detectChannelMeta(Connection c, String ownerBch, int rootNumber) throws SQLException {
ChannelMeta meta = new ChannelMeta();
meta.channelTypeVersion = CreateChannelBody.CHANNEL_TYPE_VERSION_DEFAULT & 0xFFFF;
if (rootNumber == 0) {
meta.channelName = STORIES_CHANNEL_NAME;
meta.channelDescription = detectLatestDescriptionCommand(c, ownerBch, 0);
meta.channelTypeCode = CreateChannelBody.CHANNEL_TYPE_STORIES & 0xFFFF;
return meta;
}
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;
if (!rs.next()) {
meta.channelName = null;
meta.channelDescription = "";
meta.channelTypeCode = CreateChannelBody.CHANNEL_TYPE_PUBLIC & 0xFFFF;
return meta;
}
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;
if (body instanceof CreateChannelBody ccb) {
meta.channelName = ccb.channelName;
meta.channelDescription = ccb.channelDescription == null ? "" : ccb.channelDescription;
meta.channelTypeCode = Short.toUnsignedInt(ccb.channelTypeCode);
meta.channelTypeVersion = Short.toUnsignedInt(ccb.channelTypeVersion);
} else {
meta.channelName = null;
meta.channelDescription = "";
meta.channelTypeCode = CreateChannelBody.CHANNEL_TYPE_PUBLIC & 0xFFFF;
}
String updatedDescription = detectLatestDescriptionCommand(c, ownerBch, rootNumber);
if (updatedDescription != null) {
meta.channelDescription = updatedDescription;
}
return meta;
} catch (Exception ignored) {
return null;
meta.channelName = null;
meta.channelDescription = "";
meta.channelTypeCode = CreateChannelBody.CHANNEL_TYPE_PUBLIC & 0xFFFF;
return meta;
}
}
}
@@ -168,6 +204,15 @@ final class ChannelsReadSupport {
}
}
static List<PostBlock> mergeSortedByTime(List<PostBlock> source) {
List<PostBlock> out = new ArrayList<>(source);
out.sort(Comparator
.comparingLong((PostBlock pb) -> parseTextAndTime(pb.blockBytes).createdAtMs)
.thenComparing(pb -> String.valueOf(pb.bchName))
.thenComparingInt(pb -> pb.blockNumber));
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
@@ -213,7 +258,10 @@ final class ChannelsReadSupport {
}
static String detectChannelDescription(Connection c, String ownerBch, int rootNumber) throws SQLException {
if (rootNumber == 0) return "";
if (rootNumber == 0) {
String fromCommand = detectLatestDescriptionCommand(c, ownerBch, 0);
return fromCommand == null ? "" : fromCommand;
}
// Preferred source: persisted state (fast path, works for CreateChannelBody v2).
String stateSql = """
@@ -227,7 +275,9 @@ final class ChannelsReadSupport {
ps.setInt(2, rootNumber);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return String.valueOf(rs.getString("channel_description") == null ? "" : rs.getString("channel_description"));
String saved = String.valueOf(rs.getString("channel_description") == null ? "" : rs.getString("channel_description"));
String fromCommand = detectLatestDescriptionCommand(c, ownerBch, rootNumber);
return fromCommand == null ? saved : fromCommand;
}
}
} catch (SQLException ignored) {
@@ -244,7 +294,11 @@ final class ChannelsReadSupport {
byte[] bytes = rs.getBytes("block_bytes");
BchBlockEntry e = new BchBlockEntry(bytes);
BodyRecord body = e.body;
if (body instanceof CreateChannelBody ccb) return ccb.channelDescription == null ? "" : ccb.channelDescription;
if (body instanceof CreateChannelBody ccb) {
String fromCommand = detectLatestDescriptionCommand(c, ownerBch, rootNumber);
if (fromCommand != null) return fromCommand;
return ccb.channelDescription == null ? "" : ccb.channelDescription;
}
return "";
} catch (Exception ignored) {
return "";
@@ -252,6 +306,145 @@ final class ChannelsReadSupport {
}
}
static Integer detectChannelTypeCode(Connection c, String ownerBch, int rootNumber) throws SQLException {
if (rootNumber == 0) return CreateChannelBody.CHANNEL_TYPE_STORIES & 0xFFFF;
String stateSql = """
SELECT channel_type_code
FROM channel_names_state
WHERE owner_bch_name = ? AND channel_root_block_number = ?
LIMIT 1
""";
try (PreparedStatement ps = c.prepareStatement(stateSql)) {
ps.setString(1, ownerBch);
ps.setInt(2, rootNumber);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) return rs.getInt("channel_type_code");
}
} catch (SQLException ignored) {
// fallback below
}
ChannelMeta meta = detectChannelMeta(c, ownerBch, rootNumber);
return meta.channelTypeCode;
}
static Integer detectChannelTypeVersion(Connection c, String ownerBch, int rootNumber) throws SQLException {
if (rootNumber == 0) return CreateChannelBody.CHANNEL_TYPE_VERSION_DEFAULT & 0xFFFF;
String stateSql = """
SELECT channel_type_version
FROM channel_names_state
WHERE owner_bch_name = ? AND channel_root_block_number = ?
LIMIT 1
""";
try (PreparedStatement ps = c.prepareStatement(stateSql)) {
ps.setString(1, ownerBch);
ps.setInt(2, rootNumber);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) return rs.getInt("channel_type_version");
}
} catch (SQLException ignored) {
// fallback below
}
ChannelMeta meta = detectChannelMeta(c, ownerBch, rootNumber);
return meta.channelTypeVersion;
}
static String detectLatestDescriptionCommand(Connection c, String ownerBch, int lineCode) throws SQLException {
String sql = """
SELECT block_bytes
FROM blocks
WHERE bch_name=? AND msg_type=? AND msg_sub_type=? AND line_code=?
ORDER BY block_number DESC
LIMIT 300
""";
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()) {
while (rs.next()) {
TextInfo info = parseTextAndTime(rs.getBytes("block_bytes"));
CommandInfo commandInfo = parseCommandText(info.text);
if (commandInfo != null && COMMAND_DESC.equals(commandInfo.command)) {
return commandInfo.arg;
}
}
}
}
return null;
}
static CommandInfo parseCommandText(String text) {
String value = String.valueOf(text == null ? "" : text).trim();
if (!value.startsWith(COMMAND_PREFIX)) return null;
String raw = value.substring(COMMAND_PREFIX.length()).trim();
if (raw.isEmpty()) return null;
int sp = raw.indexOf(' ');
String cmd = (sp < 0 ? raw : raw.substring(0, sp)).trim().toLowerCase();
String arg = sp < 0 ? "" : raw.substring(sp + 1).trim();
if (cmd.isEmpty()) return null;
return new CommandInfo(cmd, arg);
}
static PairChannelSelector findPersonalPairChannel(Connection c, String ownerLogin, String partnerLogin) throws SQLException {
if (ownerLogin == null || ownerLogin.isBlank() || partnerLogin == null || partnerLogin.isBlank()) return null;
String canonicalPartner = canonicalLogin(c, partnerLogin);
if (canonicalPartner == null || canonicalPartner.isBlank()) return null;
String partnerBchSql = """
SELECT blockchain_name
FROM blockchain_state
WHERE login = ? COLLATE NOCASE
ORDER BY blockchain_name
LIMIT 1
""";
String partnerBch = null;
try (PreparedStatement ps = c.prepareStatement(partnerBchSql)) {
ps.setString(1, canonicalPartner);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) partnerBch = rs.getString("blockchain_name");
}
}
if (partnerBch == null || partnerBch.isBlank()) return null;
String ownerSlug;
try {
ownerSlug = ChannelNameRules.toCanonicalSlug(ownerLogin);
} catch (Exception e) {
ownerSlug = ownerLogin.trim().toLowerCase();
}
String rootSql = """
SELECT channel_root_block_number
FROM channel_names_state
WHERE owner_bch_name = ?
AND channel_type_code = ?
AND slug = ?
ORDER BY channel_root_block_number DESC
LIMIT 1
""";
try (PreparedStatement ps = c.prepareStatement(rootSql)) {
ps.setString(1, partnerBch);
ps.setInt(2, CreateChannelBody.CHANNEL_TYPE_PERSONAL & 0xFFFF);
ps.setString(3, ownerSlug);
try (ResultSet rs = ps.executeQuery()) {
if (!rs.next()) return null;
PairChannelSelector out = new PairChannelSelector();
out.ownerBlockchainName = partnerBch;
out.channelRootBlockNumber = rs.getInt("channel_root_block_number");
return out;
}
}
}
static boolean isLikedByLogin(Connection c, String login, String toBch, int toBlockNumber, byte[] toBlockHash) throws SQLException {
if (login == null || login.isBlank() || toBch == null || toBch.isBlank() || toBlockHash == null || toBlockHash.length != 32) {
return false;
@@ -313,4 +506,26 @@ final class ChannelsReadSupport {
String text = "";
long createdAtMs = 0L;
}
static final class ChannelMeta {
String channelName;
String channelDescription;
int channelTypeCode;
int channelTypeVersion;
}
static final class CommandInfo {
final String command;
final String arg;
CommandInfo(String command, String arg) {
this.command = command;
this.arg = arg;
}
}
static final class PairChannelSelector {
String ownerBlockchainName;
int channelRootBlockNumber;
}
}
@@ -12,6 +12,7 @@ import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes;
import shine.db.SqliteDbController;
import utils.blockchain.BlockchainNameUtil;
import blockchain.body.CreateChannelBody;
import java.sql.Connection;
import java.util.ArrayList;
@@ -53,15 +54,40 @@ public class Net_GetChannelMessages_Handler implements JsonMessageHandler {
Net_GetChannelMessages_Response.Channel channel = new Net_GetChannelMessages_Response.Channel();
channel.setOwnerBlockchainName(ownerBch);
channel.setOwnerLogin(BlockchainNameUtil.loginFromBlockchainName(ownerBch));
channel.setChannelName(ChannelsReadSupport.detectChannelName(c, ownerBch, lineCode));
channel.setChannelDescription(ChannelsReadSupport.detectChannelDescription(c, ownerBch, lineCode));
ChannelsReadSupport.ChannelMeta meta = ChannelsReadSupport.detectChannelMeta(c, ownerBch, lineCode);
channel.setChannelName(meta.channelName);
channel.setChannelDescription(meta.channelDescription);
channel.setChannelTypeCode(meta.channelTypeCode);
channel.setChannelTypeVersion(meta.channelTypeVersion);
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<ChannelsReadSupport.PostBlock> posts = new ArrayList<>(
ChannelsReadSupport.channelPosts(c, ownerBch, lineCode, limit, asc)
);
if (meta.channelTypeCode == (CreateChannelBody.CHANNEL_TYPE_PERSONAL & 0xFFFF)) {
String ownerLogin = BlockchainNameUtil.loginFromBlockchainName(ownerBch);
ChannelsReadSupport.PairChannelSelector pair = ChannelsReadSupport.findPersonalPairChannel(c, ownerLogin, meta.channelName);
if (pair != null) {
posts.addAll(ChannelsReadSupport.channelPosts(
c,
pair.ownerBlockchainName,
pair.channelRootBlockNumber,
limit,
asc
));
posts = ChannelsReadSupport.mergeSortedByTime(posts);
if (!asc) {
java.util.Collections.reverse(posts);
}
if (posts.size() > limit) {
posts = new ArrayList<>(posts.subList(0, limit));
}
}
}
List<Net_GetChannelMessages_Response.MessageItem> items = new ArrayList<>();
for (ChannelsReadSupport.PostBlock post : posts) {
@@ -0,0 +1,107 @@
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_GetChannelsCounters_Request;
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetChannelsCounters_Response;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes;
import shine.db.SqliteDbController;
import shine.db.MsgSubType;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class Net_GetChannelsCounters_Handler implements JsonMessageHandler {
private static final Logger log = LoggerFactory.getLogger(Net_GetChannelsCounters_Handler.class);
@Override
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
Net_GetChannelsCounters_Request req = (Net_GetChannelsCounters_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_GetChannelsCounters_Response resp = new Net_GetChannelsCounters_Response();
resp.setOp(req.getOp());
resp.setRequestId(req.getRequestId());
resp.setStatus(WireCodes.Status.OK);
resp.setLogin(canonicalLogin);
resp.setFeedCount(countFeed(c, canonicalLogin));
resp.setDialogs100Count(countOwnedByType(c, canonicalLogin, 100));
resp.setGroupChats200Count(countOwnedByType(c, canonicalLogin, 200));
resp.setMyChannelsCount(countMyChannels(c, canonicalLogin));
return resp;
} catch (Exception e) {
log.error("GetChannelsCounters failed", e);
return NetExceptionResponseFactory.error(req, WireCodes.Status.INTERNAL_ERROR, "internal_error", "Внутренняя ошибка сервера");
}
}
private int countFeed(Connection c, String login) throws Exception {
String sql = """
SELECT COUNT(*)
FROM connections_state
WHERE login = ? COLLATE NOCASE
AND rel_type = ?
""";
try (PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, login);
ps.setInt(2, MsgSubType.CONNECTION_FOLLOW);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? rs.getInt(1) : 0;
}
}
}
private int countOwnedByType(Connection c, String login, int typeCode) throws Exception {
String sql = """
SELECT COUNT(*)
FROM channel_names_state
WHERE owner_login = ? COLLATE NOCASE
AND channel_type_code = ?
""";
try (PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, login);
ps.setInt(2, typeCode);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? rs.getInt(1) : 0;
}
}
}
private int countMyChannels(Connection c, String login) throws Exception {
String bchCountSql = "SELECT COUNT(*) FROM blockchain_state WHERE login = ? COLLATE NOCASE";
int stories = 0;
try (PreparedStatement ps = c.prepareStatement(bchCountSql)) {
ps.setString(1, login);
try (ResultSet rs = ps.executeQuery()) {
stories = rs.next() ? rs.getInt(1) : 0;
}
}
String namedSql = """
SELECT COUNT(*)
FROM channel_names_state
WHERE owner_login = ? COLLATE NOCASE
AND channel_type_code IN (1,100,200)
""";
int named = 0;
try (PreparedStatement ps = c.prepareStatement(namedSql)) {
ps.setString(1, login);
try (ResultSet rs = ps.executeQuery()) {
named = rs.next() ? rs.getInt(1) : 0;
}
}
return stories + named;
}
}
@@ -0,0 +1,217 @@
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_GetGroupDialog_Request;
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetGroupDialog_Response;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes;
import shine.db.SqliteDbController;
import shine.db.channels.ChannelNameRules;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class Net_GetGroupDialog_Handler implements JsonMessageHandler {
private static final Logger log = LoggerFactory.getLogger(Net_GetGroupDialog_Handler.class);
@Override
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
Net_GetGroupDialog_Request req = (Net_GetGroupDialog_Request) baseRequest;
if (req.getGroup() == null
|| req.getGroup().getOwnerBlockchainName() == null
|| req.getGroup().getOwnerBlockchainName().isBlank()
|| req.getGroup().getChannelRootBlockNumber() == null) {
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "bad_fields", "Некорректные поля group");
}
try (Connection c = SqliteDbController.getInstance().getConnection()) {
Net_GetGroupDialog_Response resp = new Net_GetGroupDialog_Response();
resp.setOp(req.getOp());
resp.setRequestId(req.getRequestId());
resp.setStatus(WireCodes.Status.OK);
String ownerBch = req.getGroup().getOwnerBlockchainName().trim();
int root = req.getGroup().getChannelRootBlockNumber();
fillGroupInfo(c, ownerBch, root, resp);
List<MsgRow> all = new ArrayList<>();
all.addAll(loadTextByLine(c, ownerBch, root));
for (MemberRef ref : loadActiveMembers(c, ownerBch, root)) {
GroupChannelRef target = resolveMemberChannel(c, ref);
if (target == null) continue;
all.addAll(loadTextByLine(c, target.ownerBch, target.rootNumber));
}
all.sort(Comparator.comparingLong(o -> o.createdAtMs));
resp.setMessages(toOut(all));
return resp;
} catch (Exception e) {
log.error("GetGroupDialog failed", e);
return NetExceptionResponseFactory.error(req, WireCodes.Status.INTERNAL_ERROR, "internal_error", "Внутренняя ошибка сервера");
}
}
private void fillGroupInfo(Connection c, String ownerBch, int root, Net_GetGroupDialog_Response resp) throws Exception {
String sql = """
SELECT owner_login, owner_bch_name, channel_root_block_number, channel_name, chat_title
FROM chat200_state
WHERE owner_bch_name = ? AND channel_root_block_number = ?
LIMIT 1
""";
Net_GetGroupDialog_Response.GroupInfo g = new Net_GetGroupDialog_Response.GroupInfo();
try (PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, ownerBch);
ps.setInt(2, root);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
g.setOwnerLogin(rs.getString("owner_login"));
g.setOwnerBlockchainName(rs.getString("owner_bch_name"));
g.setChannelRootBlockNumber(rs.getInt("channel_root_block_number"));
g.setChannelName(rs.getString("channel_name"));
g.setChatTitle(rs.getString("chat_title"));
resp.setGroup(g);
return;
}
}
}
g.setOwnerBlockchainName(ownerBch);
g.setChannelRootBlockNumber(root);
g.setChannelName("");
g.setChatTitle("");
resp.setGroup(g);
}
private List<MemberRef> loadActiveMembers(Connection c, String ownerBch, int root) throws Exception {
String sql = """
SELECT member_login, member_channel_name
FROM chat200_members_state
WHERE owner_bch_name = ? AND channel_root_block_number = ? AND is_active = 1
""";
List<MemberRef> out = new ArrayList<>();
try (PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, ownerBch);
ps.setInt(2, root);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
MemberRef ref = new MemberRef();
ref.memberLogin = rs.getString("member_login");
ref.memberChannelName = rs.getString("member_channel_name");
out.add(ref);
}
}
}
return out;
}
private GroupChannelRef resolveMemberChannel(Connection c, MemberRef ref) throws Exception {
String canonicalLogin = ChannelsReadSupport.canonicalLogin(c, ref.memberLogin);
if (canonicalLogin == null || canonicalLogin.isBlank()) return null;
String bchSql = "SELECT blockchain_name FROM blockchain_state WHERE login = ? COLLATE NOCASE ORDER BY blockchain_name LIMIT 1";
String memberBch = null;
try (PreparedStatement ps = c.prepareStatement(bchSql)) {
ps.setString(1, canonicalLogin);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) memberBch = rs.getString("blockchain_name");
}
}
if (memberBch == null || memberBch.isBlank()) return null;
String slug;
try {
slug = ChannelNameRules.toCanonicalSlug(ref.memberChannelName);
} catch (Exception e) {
return null;
}
String rootSql = """
SELECT channel_root_block_number
FROM channel_names_state
WHERE owner_bch_name = ? AND channel_type_code = 200 AND slug = ?
ORDER BY channel_root_block_number DESC
LIMIT 1
""";
try (PreparedStatement ps = c.prepareStatement(rootSql)) {
ps.setString(1, memberBch);
ps.setString(2, slug);
try (ResultSet rs = ps.executeQuery()) {
if (!rs.next()) return null;
GroupChannelRef out = new GroupChannelRef();
out.ownerBch = memberBch;
out.rootNumber = rs.getInt("channel_root_block_number");
return out;
}
}
}
private List<MsgRow> loadTextByLine(Connection c, String ownerBch, int line) throws Exception {
String sql = """
SELECT login, bch_name, block_number, block_hash, block_bytes
FROM blocks
WHERE bch_name = ? AND msg_type = 1 AND line_code = ?
ORDER BY block_number ASC
""";
List<MsgRow> out = new ArrayList<>();
try (PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, ownerBch);
ps.setInt(2, line);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
MsgRow row = new MsgRow();
row.authorLogin = rs.getString("login");
row.authorBch = rs.getString("bch_name");
row.blockNumber = rs.getInt("block_number");
row.blockHash = rs.getBytes("block_hash");
ChannelsReadSupport.TextInfo ti = ChannelsReadSupport.parseTextAndTime(rs.getBytes("block_bytes"));
row.createdAtMs = ti.createdAtMs;
row.text = ti.text;
out.add(row);
}
}
}
return out;
}
private List<Net_GetGroupDialog_Response.MessageItem> toOut(List<MsgRow> rows) {
List<Net_GetGroupDialog_Response.MessageItem> out = new ArrayList<>();
for (MsgRow r : rows) {
Net_GetGroupDialog_Response.MessageItem item = new Net_GetGroupDialog_Response.MessageItem();
item.setAuthorLogin(r.authorLogin);
item.setAuthorBlockchainName(r.authorBch);
item.setBlockNumber(r.blockNumber);
item.setBlockHash(ChannelsReadSupport.toHex(r.blockHash));
item.setCreatedAtMs(r.createdAtMs);
item.setText(r.text);
out.add(item);
}
return out;
}
private static final class MemberRef {
String memberLogin;
String memberChannelName;
}
private static final class GroupChannelRef {
String ownerBch;
int rootNumber;
}
private static final class MsgRow {
String authorLogin;
String authorBch;
int blockNumber;
byte[] blockHash;
long createdAtMs;
String text;
}
}
@@ -0,0 +1,86 @@
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_ListGroupChats200_Request;
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_ListGroupChats200_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.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class Net_ListGroupChats200_Handler implements JsonMessageHandler {
private static final Logger log = LoggerFactory.getLogger(Net_ListGroupChats200_Handler.class);
@Override
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
Net_ListGroupChats200_Request req = (Net_ListGroupChats200_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_ListGroupChats200_Response resp = new Net_ListGroupChats200_Response();
resp.setOp(req.getOp());
resp.setRequestId(req.getRequestId());
resp.setStatus(WireCodes.Status.OK);
resp.setLogin(canonicalLogin);
resp.setChats(loadRows(c, canonicalLogin));
return resp;
} catch (Exception e) {
log.error("ListGroupChats200 failed", e);
return NetExceptionResponseFactory.error(req, WireCodes.Status.INTERNAL_ERROR, "internal_error", "Внутренняя ошибка сервера");
}
}
private List<Net_ListGroupChats200_Response.Row> loadRows(Connection c, String login) throws Exception {
String sql = """
SELECT s.owner_login, s.owner_bch_name, s.channel_root_block_number, s.channel_root_block_hash,
s.channel_name, s.chat_title, s.updated_at_ms,
COALESCE(m.members_count, 0) AS members_count
FROM chat200_state s
LEFT JOIN (
SELECT owner_bch_name, channel_root_block_number, COUNT(*) AS members_count
FROM chat200_members_state
WHERE is_active = 1
GROUP BY owner_bch_name, channel_root_block_number
) m
ON m.owner_bch_name = s.owner_bch_name
AND m.channel_root_block_number = s.channel_root_block_number
WHERE s.owner_login = ? COLLATE NOCASE
ORDER BY s.updated_at_ms DESC, s.channel_root_block_number DESC
""";
List<Net_ListGroupChats200_Response.Row> out = new ArrayList<>();
try (PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, login);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
Net_ListGroupChats200_Response.Row row = new Net_ListGroupChats200_Response.Row();
row.setOwnerLogin(rs.getString("owner_login"));
row.setOwnerBlockchainName(rs.getString("owner_bch_name"));
row.setChannelRootBlockNumber(rs.getInt("channel_root_block_number"));
row.setChannelRootBlockHash(ChannelsReadSupport.toHex(rs.getBytes("channel_root_block_hash")));
row.setChannelName(rs.getString("channel_name"));
row.setChatTitle(rs.getString("chat_title"));
row.setUpdatedAtMs(rs.getLong("updated_at_ms"));
row.setMembersCount(rs.getInt("members_count"));
out.add(row);
}
}
}
return out;
}
}
@@ -61,11 +61,14 @@ public class Net_ListSubscriptionsFeed_Handler implements JsonMessageHandler {
for (ChannelKey key : keys) {
Net_ListSubscriptionsFeed_Response.ChannelSummary row = new Net_ListSubscriptionsFeed_Response.ChannelSummary();
Net_ListSubscriptionsFeed_Response.ChannelRef channelRef = new Net_ListSubscriptionsFeed_Response.ChannelRef();
ChannelsReadSupport.ChannelMeta meta = ChannelsReadSupport.detectChannelMeta(c, key.ownerBch, key.rootNumber);
channelRef.setOwnerLogin(key.ownerLogin);
channelRef.setOwnerBlockchainName(key.ownerBch);
channelRef.setChannelName(ChannelsReadSupport.detectChannelName(c, key.ownerBch, key.rootNumber));
channelRef.setChannelDescription(ChannelsReadSupport.detectChannelDescription(c, key.ownerBch, key.rootNumber));
channelRef.setPersonal(key.rootNumber == 0);
channelRef.setChannelName(meta.channelName);
channelRef.setChannelDescription(meta.channelDescription);
channelRef.setChannelTypeCode(meta.channelTypeCode);
channelRef.setChannelTypeVersion(meta.channelTypeVersion);
channelRef.setPersonal(meta.channelTypeCode == 100);
Net_ListSubscriptionsFeed_Response.BlockRef rootRef = new Net_ListSubscriptionsFeed_Response.BlockRef();
rootRef.setBlockNumber(key.rootNumber);
@@ -21,6 +21,8 @@ public class Net_GetChannelMessages_Response extends Net_Response {
private String ownerBlockchainName;
private String channelName;
private String channelDescription;
private Integer channelTypeCode;
private Integer channelTypeVersion;
private BlockRef channelRoot;
public String getOwnerLogin() { return ownerLogin; }
@@ -35,6 +37,12 @@ public class Net_GetChannelMessages_Response extends Net_Response {
public String getChannelDescription() { return channelDescription; }
public void setChannelDescription(String channelDescription) { this.channelDescription = channelDescription; }
public Integer getChannelTypeCode() { return channelTypeCode; }
public void setChannelTypeCode(Integer channelTypeCode) { this.channelTypeCode = channelTypeCode; }
public Integer getChannelTypeVersion() { return channelTypeVersion; }
public void setChannelTypeVersion(Integer channelTypeVersion) { this.channelTypeVersion = channelTypeVersion; }
public BlockRef getChannelRoot() { return channelRoot; }
public void setChannelRoot(BlockRef channelRoot) { this.channelRoot = channelRoot; }
}
@@ -0,0 +1,11 @@
package server.logic.ws_protocol.JSON.handlers.channels.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
public class Net_GetChannelsCounters_Request extends Net_Request {
private String login;
public String getLogin() { return login; }
public void setLogin(String login) { this.login = login; }
}
@@ -0,0 +1,23 @@
package server.logic.ws_protocol.JSON.handlers.channels.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
public class Net_GetChannelsCounters_Response extends Net_Response {
private String login;
private int feedCount;
private int dialogs100Count;
private int groupChats200Count;
private int myChannelsCount;
public String getLogin() { return login; }
public void setLogin(String login) { this.login = login; }
public int getFeedCount() { return feedCount; }
public void setFeedCount(int feedCount) { this.feedCount = feedCount; }
public int getDialogs100Count() { return dialogs100Count; }
public void setDialogs100Count(int dialogs100Count) { this.dialogs100Count = dialogs100Count; }
public int getGroupChats200Count() { return groupChats200Count; }
public void setGroupChats200Count(int groupChats200Count) { this.groupChats200Count = groupChats200Count; }
public int getMyChannelsCount() { return myChannelsCount; }
public void setMyChannelsCount(int myChannelsCount) { this.myChannelsCount = myChannelsCount; }
}
@@ -0,0 +1,24 @@
package server.logic.ws_protocol.JSON.handlers.channels.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
public class Net_GetGroupDialog_Request extends Net_Request {
private String login;
private GroupSelector group;
public String getLogin() { return login; }
public void setLogin(String login) { this.login = login; }
public GroupSelector getGroup() { return group; }
public void setGroup(GroupSelector group) { this.group = group; }
public static class GroupSelector {
private String ownerBlockchainName;
private Integer channelRootBlockNumber;
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; }
}
}
@@ -0,0 +1,58 @@
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_GetGroupDialog_Response extends Net_Response {
private GroupInfo group;
private List<MessageItem> messages = new ArrayList<>();
public GroupInfo getGroup() { return group; }
public void setGroup(GroupInfo group) { this.group = group; }
public List<MessageItem> getMessages() { return messages; }
public void setMessages(List<MessageItem> messages) { this.messages = messages; }
public static class GroupInfo {
private String ownerLogin;
private String ownerBlockchainName;
private int channelRootBlockNumber;
private String channelName;
private String chatTitle;
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 int getChannelRootBlockNumber() { return channelRootBlockNumber; }
public void setChannelRootBlockNumber(int channelRootBlockNumber) { this.channelRootBlockNumber = channelRootBlockNumber; }
public String getChannelName() { return channelName; }
public void setChannelName(String channelName) { this.channelName = channelName; }
public String getChatTitle() { return chatTitle; }
public void setChatTitle(String chatTitle) { this.chatTitle = chatTitle; }
}
public static class MessageItem {
private String authorLogin;
private String authorBlockchainName;
private int blockNumber;
private String blockHash;
private long createdAtMs;
private String text;
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 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 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; }
}
}
@@ -0,0 +1,11 @@
package server.logic.ws_protocol.JSON.handlers.channels.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
public class Net_ListGroupChats200_Request extends Net_Request {
private String login;
public String getLogin() { return login; }
public void setLogin(String login) { this.login = login; }
}
@@ -0,0 +1,46 @@
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_ListGroupChats200_Response extends Net_Response {
private String login;
private List<Row> chats = new ArrayList<>();
public String getLogin() { return login; }
public void setLogin(String login) { this.login = login; }
public List<Row> getChats() { return chats; }
public void setChats(List<Row> chats) { this.chats = chats; }
public static class Row {
private String ownerLogin;
private String ownerBlockchainName;
private int channelRootBlockNumber;
private String channelRootBlockHash;
private String channelName;
private String chatTitle;
private int membersCount;
private long updatedAtMs;
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 int getChannelRootBlockNumber() { return channelRootBlockNumber; }
public void setChannelRootBlockNumber(int channelRootBlockNumber) { this.channelRootBlockNumber = channelRootBlockNumber; }
public String getChannelRootBlockHash() { return channelRootBlockHash; }
public void setChannelRootBlockHash(String channelRootBlockHash) { this.channelRootBlockHash = channelRootBlockHash; }
public String getChannelName() { return channelName; }
public void setChannelName(String channelName) { this.channelName = channelName; }
public String getChatTitle() { return chatTitle; }
public void setChatTitle(String chatTitle) { this.chatTitle = chatTitle; }
public int getMembersCount() { return membersCount; }
public void setMembersCount(int membersCount) { this.membersCount = membersCount; }
public long getUpdatedAtMs() { return updatedAtMs; }
public void setUpdatedAtMs(long updatedAtMs) { this.updatedAtMs = updatedAtMs; }
}
}
@@ -47,6 +47,8 @@ public class Net_ListSubscriptionsFeed_Response extends Net_Response {
private String ownerBlockchainName;
private String channelName;
private String channelDescription;
private Integer channelTypeCode;
private Integer channelTypeVersion;
private boolean personal;
private BlockRef channelRoot;
@@ -62,6 +64,12 @@ public class Net_ListSubscriptionsFeed_Response extends Net_Response {
public String getChannelDescription() { return channelDescription; }
public void setChannelDescription(String channelDescription) { this.channelDescription = channelDescription; }
public Integer getChannelTypeCode() { return channelTypeCode; }
public void setChannelTypeCode(Integer channelTypeCode) { this.channelTypeCode = channelTypeCode; }
public Integer getChannelTypeVersion() { return channelTypeVersion; }
public void setChannelTypeVersion(Integer channelTypeVersion) { this.channelTypeVersion = channelTypeVersion; }
public boolean isPersonal() { return personal; }
public void setPersonal(boolean personal) { this.personal = personal; }