From e17a6765ec7a1eba9664c6146e4c95d72f3864b3d2e283f5f44d35ddd931937a Mon Sep 17 00:00:00 2001 From: DrygMira Date: Tue, 14 Apr 2026 13:47:59 +0300 Subject: [PATCH] Fix channel/author subscription confirmation and follow trigger persistence --- shine-UI/js/pages/channels-list.js | 88 ++++++++++++++++--- .../shine/db/DatabaseTriggersInstaller.java | 69 +++++++++++++-- 2 files changed, 137 insertions(+), 20 deletions(-) diff --git a/shine-UI/js/pages/channels-list.js b/shine-UI/js/pages/channels-list.js index ca6bfcf..d250969 100644 --- a/shine-UI/js/pages/channels-list.js +++ b/shine-UI/js/pages/channels-list.js @@ -111,18 +111,23 @@ async function resolveChannelTargetFromInput(rawInput) { } const ownerFeed = await authService.listSubscriptionsFeed(ownerLogin, 500); - const ownChannels = (ownerFeed?.ownedChannels || []).filter((item) => ( - String(item?.channel?.ownerBlockchainName || '') === String(user.blockchainName) - )); - - const match = ownChannels.find((item) => ( + const ownChannels = Array.isArray(ownerFeed?.ownedChannels) ? ownerFeed.ownedChannels : []; + const matches = ownChannels.filter((item) => ( String(item?.channel?.channelName || '').trim().toLowerCase() === channelName )); - if (!match) { + if (!matches.length) { throw new Error('Канал не найден у указанного автора.'); } + const primaryMatches = matches.filter((item) => ( + String(item?.channel?.ownerBlockchainName || '') === String(user.blockchainName || '') + )); + const pool = primaryMatches.length ? primaryMatches : matches; + const match = [...pool].sort((a, b) => ( + Number(b?.channel?.channelRoot?.blockNumber || -1) - Number(a?.channel?.channelRoot?.blockNumber || -1) + ))[0]; + return { ownerBlockchainName: String(match?.channel?.ownerBlockchainName || user.blockchainName), rootBlockNumber: Number(match?.channel?.channelRoot?.blockNumber), @@ -195,6 +200,36 @@ function renderSuggestions(container, values, onPick) { }); } +function normalizeComparableLogin(value) { + return normalizeLoginInput(value).toLowerCase(); +} + +function isFollowedUserVisible(targetLogin) { + const expected = normalizeComparableLogin(targetLogin); + if (!expected) return false; + const rows = Array.isArray(state.channelsFeed?.followedUsersChannels) + ? state.channelsFeed.followedUsersChannels + : []; + return rows.some((row) => normalizeComparableLogin(row?.channel?.ownerLogin) === expected); +} + +function isFollowedChannelVisible(target) { + const rows = Array.isArray(state.channelsFeed?.followedChannels) + ? state.channelsFeed.followedChannels + : []; + const expectedBch = String(target?.ownerBlockchainName || ''); + const expectedNo = Number(target?.rootBlockNumber); + const expectedHash = normalizeHash(target?.rootBlockHash); + if (!expectedBch || !Number.isFinite(expectedNo)) return false; + + return rows.some((row) => { + const rowBch = String(row?.channel?.ownerBlockchainName || ''); + const rowNo = Number(row?.channel?.channelRoot?.blockNumber); + const rowHash = normalizeHash(row?.channel?.channelRoot?.blockHash); + return rowBch === expectedBch && rowNo === expectedNo && rowHash === expectedHash; + }); +} + function openSimpleSubscribeModal({ kind, kindLabel, submitLabel, unfollow = false, onSuccess }) { const targetHint = kind === 'channel' ? '

Канал: user/channel, имя канала или bch:number:hash.

' @@ -306,35 +341,62 @@ function openSimpleSubscribeModal({ kind, kindLabel, submitLabel, unfollow = fal errorEl.textContent = ''; try { + let channelTarget = null; + let userTargetLogin = ''; + if (kind === 'user') { + userTargetLogin = normalizeLoginInput(value); await authService.addBlockFollowUser({ login, - targetLogin: normalizeLoginInput(value), + targetLogin: userTargetLogin, storagePwd, unfollow, }); } else if (kind === 'channel') { - const target = await resolveChannelTargetFromInput(value); - if (!target?.ownerBlockchainName || !Number.isFinite(target.rootBlockNumber)) { + channelTarget = await resolveChannelTargetFromInput(value); + if (!channelTarget?.ownerBlockchainName || !Number.isFinite(channelTarget.rootBlockNumber)) { throw new Error('Канал не найден.'); } await authService.addBlockFollowChannel({ login, storagePwd, - targetBlockchainName: target.ownerBlockchainName, - targetBlockNumber: target.rootBlockNumber, - targetBlockHashHex: target.rootBlockHash, + targetBlockchainName: channelTarget.ownerBlockchainName, + targetBlockNumber: channelTarget.rootBlockNumber, + targetBlockHashHex: channelTarget.rootBlockHash, unfollow, }); } else { throw new Error('Неподдерживаемый тип подписки'); } + if (typeof onSuccess === 'function') { + await onSuccess(); + } + + if (kind === 'user') { + const visible = isFollowedUserVisible(userTargetLogin); + if (!unfollow && !visible) { + throw new Error('Подписка не подтвердилась после обновления списка.'); + } + if (unfollow && visible) { + throw new Error('Отписка не подтвердилась после обновления списка.'); + } + } + + if (kind === 'channel') { + const visible = isFollowedChannelVisible(channelTarget); + if (!unfollow && !visible) { + throw new Error('Подписка на канал не подтвердилась после обновления списка.'); + } + if (unfollow && visible) { + throw new Error('Отписка от канала не подтвердилась после обновления списка.'); + } + } + softHaptic(15); showToast(unfollow ? 'Отписка выполнена' : 'Подписка выполнена'); close(); - if (typeof onSuccess === 'function') onSuccess(); } catch (error) { errorEl.textContent = toUserMessage(error, `${submitText} не удалось.`); setBusy(false); diff --git a/shine-server-db/src/main/java/shine/db/DatabaseTriggersInstaller.java b/shine-server-db/src/main/java/shine/db/DatabaseTriggersInstaller.java index 830267c..8252e3f 100644 --- a/shine-server-db/src/main/java/shine/db/DatabaseTriggersInstaller.java +++ b/shine-server-db/src/main/java/shine/db/DatabaseTriggersInstaller.java @@ -212,12 +212,30 @@ public final class DatabaseTriggersInstaller { SELECT NEW.login, NEW.msg_sub_type, - NEW.to_login, + COALESCE( + NEW.to_login, + CASE + WHEN NEW.to_bch_name IS NOT NULL + AND length(NEW.to_bch_name) > 4 + AND substr(NEW.to_bch_name, length(NEW.to_bch_name) - 3, 1) = '-' + THEN substr(NEW.to_bch_name, 1, length(NEW.to_bch_name) - 4) + ELSE NULL + END + ), NEW.to_bch_name, NEW.to_block_number, NEW.to_block_hash WHERE NEW.msg_sub_type IN (%d, %d, %d) - AND NEW.to_login IS NOT NULL + AND COALESCE( + NEW.to_login, + CASE + WHEN NEW.to_bch_name IS NOT NULL + AND length(NEW.to_bch_name) > 4 + AND substr(NEW.to_bch_name, length(NEW.to_bch_name) - 3, 1) = '-' + THEN substr(NEW.to_bch_name, 1, length(NEW.to_bch_name) - 4) + ELSE NULL + END + ) IS NOT NULL AND NEW.to_bch_name IS NOT NULL; -- 2) если запись есть — обновляем актуальные to_* @@ -228,27 +246,64 @@ public final class DatabaseTriggersInstaller { to_block_hash = NEW.to_block_hash WHERE login = NEW.login AND rel_type = NEW.msg_sub_type - AND to_login = NEW.to_login - AND NEW.msg_sub_type IN (%d, %d, %d) - AND NEW.to_login IS NOT NULL + AND to_login = COALESCE( + NEW.to_login, + CASE + WHEN NEW.to_bch_name IS NOT NULL + AND length(NEW.to_bch_name) > 4 + AND substr(NEW.to_bch_name, length(NEW.to_bch_name) - 3, 1) = '-' + THEN substr(NEW.to_bch_name, 1, length(NEW.to_bch_name) - 4) + ELSE NULL + END + ) + AND NEW.msg_sub_type IN (%d, %d) + AND COALESCE( + NEW.to_login, + CASE + WHEN NEW.to_bch_name IS NOT NULL + AND length(NEW.to_bch_name) > 4 + AND substr(NEW.to_bch_name, length(NEW.to_bch_name) - 3, 1) = '-' + THEN substr(NEW.to_bch_name, 1, length(NEW.to_bch_name) - 4) + ELSE NULL + END + ) IS NOT NULL AND NEW.to_bch_name IS NOT NULL; -- UNFRIEND/UNCONTACT/UNFOLLOW: -- удаляем соответствующее "позитивное" состояние DELETE FROM connections_state WHERE login = NEW.login - AND to_login = NEW.to_login + AND to_login = COALESCE( + NEW.to_login, + CASE + WHEN NEW.to_bch_name IS NOT NULL + AND length(NEW.to_bch_name) > 4 + AND substr(NEW.to_bch_name, length(NEW.to_bch_name) - 3, 1) = '-' + THEN substr(NEW.to_bch_name, 1, length(NEW.to_bch_name) - 4) + ELSE NULL + END + ) AND rel_type = CASE NEW.msg_sub_type WHEN %d THEN %d WHEN %d THEN %d WHEN %d THEN %d ELSE rel_type END + AND COALESCE( + NEW.to_login, + CASE + WHEN NEW.to_bch_name IS NOT NULL + AND length(NEW.to_bch_name) > 4 + AND substr(NEW.to_bch_name, length(NEW.to_bch_name) - 3, 1) = '-' + THEN substr(NEW.to_bch_name, 1, length(NEW.to_bch_name) - 4) + ELSE NULL + END + ) IS NOT NULL AND NEW.msg_sub_type IN (%d, %d, %d); END; """.formatted( FRIEND, CONTACT, FOLLOW, - FRIEND, CONTACT, FOLLOW, + FRIEND, CONTACT, UNFRIEND, FRIEND, UNCONTACT, CONTACT,