diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/ChannelsReadSupport.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/ChannelsReadSupport.java index a3a2d560..0c59bfd1 100644 --- a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/ChannelsReadSupport.java +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/ChannelsReadSupport.java @@ -309,15 +309,42 @@ final class ChannelsReadSupport { static int[] loadStats(Connection c, String bch, int blockNumber, byte[] blockHash) throws SQLException { String sql = "SELECT likes_count,replies_count FROM message_stats WHERE to_bch_name=? AND to_block_number=? AND to_block_hash=? LIMIT 1"; + int likesCount = 0; + int repliesCount = 0; try (PreparedStatement ps = c.prepareStatement(sql)) { ps.setString(1, bch); ps.setInt(2, blockNumber); ps.setBytes(3, blockHash); try (ResultSet rs = ps.executeQuery()) { - if (!rs.next()) return new int[] {0, 0}; - return new int[] {rs.getInt("likes_count"), rs.getInt("replies_count")}; + if (rs.next()) { + likesCount = rs.getInt("likes_count"); + repliesCount = rs.getInt("replies_count"); + } } } + String ratingsSql = """ + SELECT COUNT(*) + FROM blocks + WHERE msg_type = ? + AND msg_sub_type = ? + AND to_bch_name = ? + AND to_block_number = ? + AND to_block_hash = ? + """; + int ratingsCount = 0; + try (PreparedStatement ps = c.prepareStatement(ratingsSql)) { + ps.setInt(1, MSG_TYPE_TEXT); + ps.setInt(2, MsgSubType.TEXT_RATING); + ps.setString(3, bch); + ps.setInt(4, blockNumber); + ps.setBytes(5, blockHash); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + ratingsCount = rs.getInt(1); + } + } + } + return new int[] {likesCount, repliesCount, ratingsCount}; } static String detectChannelDescription(Connection c, String ownerBch, int rootNumber) throws SQLException { diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/Net_GetChannelMessages_Handler.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/Net_GetChannelMessages_Handler.java index 882ad706..c3b063cd 100644 --- a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/Net_GetChannelMessages_Handler.java +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/Net_GetChannelMessages_Handler.java @@ -167,6 +167,7 @@ public class Net_GetChannelMessages_Handler implements JsonMessageHandler { int[] stats = ChannelsReadSupport.loadStats(c, ownerBch, post.blockNumber, post.blockHash); item.setLikesCount(stats[0]); item.setRepliesCount(stats[1]); + item.setRatingsCount(stats[2]); item.setLikedByMe(ChannelsReadSupport.isLikedByLogin(c, viewerLogin, post.bchName, post.blockNumber, post.blockHash)); items.add(item); diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/Net_GetMessageThread_Handler.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/Net_GetMessageThread_Handler.java index 83b045b1..fe10d183 100644 --- a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/Net_GetMessageThread_Handler.java +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/Net_GetMessageThread_Handler.java @@ -17,6 +17,7 @@ import shine.db.DbController; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.util.Comparator; import java.util.ArrayList; import java.util.Base64; import java.util.List; @@ -89,7 +90,7 @@ public class Net_GetMessageThread_Handler implements JsonMessageHandler { private List loadChildren(Connection c, PostRow parent, int depthDown, int childLimit, String viewerLogin) throws Exception { if (depthDown <= 0) return List.of(); - List replies = findReplies(c, parent.bchName, parent.blockNumber, parent.blockHash, childLimit); + List replies = findRepliesAndRatings(c, parent.bchName, parent.blockNumber, parent.blockHash, childLimit); List out = new ArrayList<>(); for (PostRow row : replies) { Net_GetMessageThread_Response.MessageNodeTree t = new Net_GetMessageThread_Response.MessageNodeTree(); @@ -100,24 +101,29 @@ public class Net_GetMessageThread_Handler implements JsonMessageHandler { return out; } - private List findReplies(Connection c, String toBchName, int toBlockNumber, byte[] toBlockHash, int limit) throws Exception { + private List findRepliesAndRatings(Connection c, String toBchName, int toBlockNumber, byte[] toBlockHash, int limit) throws Exception { String sql = """ SELECT login,bch_name,block_number,block_hash,block_bytes,to_bch_name,to_block_number,to_block_hash,line_code,msg_sub_type,this_line_number FROM blocks - WHERE msg_type=1 AND msg_sub_type=? + WHERE msg_type=1 AND msg_sub_type IN (?, ?) AND to_bch_name=? AND to_block_number=? AND to_block_hash=? - ORDER BY block_number ASC - LIMIT ? """; try (PreparedStatement ps = c.prepareStatement(sql)) { ps.setInt(1, MsgSubType.TEXT_REPLY); - ps.setString(2, toBchName); - ps.setInt(3, toBlockNumber); - ps.setBytes(4, toBlockHash); - ps.setInt(5, limit); + ps.setInt(2, MsgSubType.TEXT_RATING); + ps.setString(3, toBchName); + ps.setInt(4, toBlockNumber); + ps.setBytes(5, toBlockHash); try (ResultSet rs = ps.executeQuery()) { List out = new ArrayList<>(); while (rs.next()) out.add(mapRow(rs)); + out.sort(Comparator + .comparingLong((PostRow row) -> ChannelsReadSupport.parseTextAndTime(row.blockBytes).createdAtMs) + .thenComparing(row -> String.valueOf(row.bchName)) + .thenComparingInt(row -> row.blockNumber)); + if (out.size() > limit) { + return new ArrayList<>(out.subList(0, limit)); + } return out; } } @@ -233,6 +239,7 @@ public class Net_GetMessageThread_Handler implements JsonMessageHandler { int[] stats = ChannelsReadSupport.loadStats(c, row.bchName, row.blockNumber, row.blockHash); node.setLikesCount(stats[0]); node.setRepliesCount(stats[1]); + node.setRatingsCount(stats[2]); node.setLikedByMe(ChannelsReadSupport.isLikedByLogin(c, viewerLogin, row.bchName, row.blockNumber, row.blockHash)); if (row.lineCode != null && row.lineCode >= 0) { Net_GetMessageThread_Response.ChannelInfo ci = new Net_GetMessageThread_Response.ChannelInfo(); diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/entyties/Net_GetChannelMessages_Response.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/entyties/Net_GetChannelMessages_Response.java index 9c1a80ff..35116490 100644 --- a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/entyties/Net_GetChannelMessages_Response.java +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/entyties/Net_GetChannelMessages_Response.java @@ -132,6 +132,7 @@ public class Net_GetChannelMessages_Response extends Net_Response { private int likesCount; private boolean likedByMe; private int repliesCount; + private int ratingsCount; private int versionsTotal; private List versions = new ArrayList<>(); @@ -173,6 +174,9 @@ public class Net_GetChannelMessages_Response extends Net_Response { public int getRepliesCount() { return repliesCount; } public void setRepliesCount(int repliesCount) { this.repliesCount = repliesCount; } + public int getRatingsCount() { return ratingsCount; } + public void setRatingsCount(int ratingsCount) { this.ratingsCount = ratingsCount; } + public int getVersionsTotal() { return versionsTotal; } public void setVersionsTotal(int versionsTotal) { this.versionsTotal = versionsTotal; } diff --git a/VERSION.properties b/VERSION.properties index 2e486484..3ec10981 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.5.9 -server.version=1.4.7 +client.version=1.5.10 +server.version=1.4.8 diff --git a/docs/API/06_Channels_Read_API.md b/docs/API/06_Channels_Read_API.md index ad6c30d3..5526458d 100644 --- a/docs/API/06_Channels_Read_API.md +++ b/docs/API/06_Channels_Read_API.md @@ -192,6 +192,7 @@ "text": "текущая версия", "likesCount": 12, "repliesCount": 3, + "ratingsCount": 2, "versionsTotal": 4, "versions": [ { "versionIndex": 1, "blockNumber": 140, "blockHash": "...", "text": "v1", "createdAtMs": 1760000000000 }, @@ -248,6 +249,13 @@ - `rawBlockB64` — сырой `block_bytes` текущего блока в Base64. - Поле `rawBlockB64` присутствует у узлов во всех частях ответа `GetMessageThread`: `focus`, `ancestors[]`, `descendants[]`. - В `GetChannelMessages` поле `rawBlockB64` **не добавляется** (лента канала без сырого блока, чтобы не раздувать ответ). +- И в `GetChannelMessages`, и в `GetMessageThread` каждое сообщение теперь содержит: + - `repliesCount` — число дочерних сообщений типа `TEXT_REPLY`; + - `ratingsCount` — число дочерних сообщений типа `TEXT_RATING`. +- В `descendants[]` операции `GetMessageThread` возвращаются оба типа дочерних текстовых сообщений: + - `TEXT_REPLY`; + - `TEXT_RATING`. + Они идут в одной общей ветке обсуждения и сортируются по времени создания. --- diff --git a/docs/Blockchain/CHANGELOG.md b/docs/Blockchain/CHANGELOG.md index fe70c227..77694b8c 100644 --- a/docs/Blockchain/CHANGELOG.md +++ b/docs/Blockchain/CHANGELOG.md @@ -1,5 +1,12 @@ # История изменений документации блокчейна +## 2026-08-09 19:40:00 +0400 +- Базовый коммит-ориентир: `3552e05`. +- Уточнено серверное чтение каналов и тредов для `TEXT_RATING`: + - `GetChannelMessages` и `GetMessageThread` теперь отдают отдельное поле `ratingsCount`; + - `GetMessageThread` включает `TEXT_RATING` в общее дерево потомков вместе с `TEXT_REPLY`; + - в `docs/API/06_Channels_Read_API.md` зафиксировано, что потомки треда возвращаются вперемешку по времени создания. + ## 2026-08-09 18:55:16 +0400 - Базовый коммит-ориентир: `43f54c9`. - Для первой итерации новых контентных типов обновлена карта `TEXT`-подтипов: diff --git a/shine-UI/js/pages/channel-thread-view.js b/shine-UI/js/pages/channel-thread-view.js index d179abce..1a4c63fd 100644 --- a/shine-UI/js/pages/channel-thread-view.js +++ b/shine-UI/js/pages/channel-thread-view.js @@ -24,6 +24,11 @@ import { loadProfileSnapshot } from '../services/user-profile-params.js'; import { extractLoginFromBlockchainName, makeProfileRoute, makeShineChannelRoute, makeShineMessageRoute } from '../services/shine-routes.js'; export const pageMeta = { id: 'channel-thread-view', title: 'Тред' }; +const MSG_SUBTYPE_TEXT_RATING = 30; +const MSG_SUBTYPE_TEXT_ENTRYPOINT = 100; +const MSG_SUBTYPE_TEXT_EXERCISE = 110; +const MSG_SUBTYPE_TEXT_SERVICE = 120; +const MSG_SUBTYPE_TEXT_COURSE = 130; const pendingReactionActions = new Set(); const pendingThreadScroll = new Map(); @@ -227,6 +232,21 @@ function resolveChannelHeadingFromNode(node) { return `Сообщение в канале ${channelName}`; } +function getChannelMessageTypeMeta(msgSubType) { + switch (Number(msgSubType || 0)) { + case MSG_SUBTYPE_TEXT_EXERCISE: + return { label: 'Упражнение' }; + case MSG_SUBTYPE_TEXT_SERVICE: + return { label: 'Процедура' }; + case MSG_SUBTYPE_TEXT_COURSE: + return { label: 'Курс' }; + case MSG_SUBTYPE_TEXT_ENTRYPOINT: + return { label: 'Оглавление' }; + default: + return null; + } +} + function extractChannelContextFromThreadPayload(payload) { const focusInfo = payload?.focus?.channelInfo; if (focusInfo?.ownerBlockchainName && focusInfo?.channelRoot?.blockNumber != null) { @@ -461,13 +481,22 @@ function renderDraftAttachments(container, attachments) { }); } -function openReplyModal({ onSubmit, navigate }) { +function openReplyModal({ onSubmit, navigate, mode = 'reply' }) { + const isRating = mode === 'rating'; + const title = isRating ? 'Оценка' : 'Ответ'; + const placeholder = isRating ? 'Текст оценки' : 'Текст ответа'; + const emptyError = isRating + ? 'Введите текст оценки или добавьте вложение.' + : 'Введите текст ответа или добавьте вложение.'; + const submitError = isRating + ? 'Не удалось отправить оценку.' + : 'Не удалось отправить ответ.'; const root = document.getElementById('modal-root'); root.innerHTML = `