SHA256
Каналы: типы 0/1/100/200, CreateChannel v3, state для chat200, новые API и деплой на prod
This commit is contained in:
+12
@@ -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),
|
||||
|
||||
+173
-15
@@ -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");
|
||||
|
||||
+9
-2
@@ -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);
|
||||
|
||||
+224
-9
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+29
-3
@@ -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) {
|
||||
|
||||
+107
@@ -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;
|
||||
}
|
||||
}
|
||||
+217
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+86
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-3
@@ -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);
|
||||
|
||||
+8
@@ -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; }
|
||||
}
|
||||
|
||||
+11
@@ -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; }
|
||||
}
|
||||
|
||||
+23
@@ -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; }
|
||||
}
|
||||
|
||||
+24
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
+58
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -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; }
|
||||
}
|
||||
|
||||
+46
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -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; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user