Fix channel/author subscription confirmation and follow trigger persistence

This commit is contained in:
DrygMira 2026-04-14 13:47:59 +03:00
parent 7bdb3118ae
commit e17a6765ec
2 changed files with 137 additions and 20 deletions

View File

@ -111,18 +111,23 @@ async function resolveChannelTargetFromInput(rawInput) {
} }
const ownerFeed = await authService.listSubscriptionsFeed(ownerLogin, 500); const ownerFeed = await authService.listSubscriptionsFeed(ownerLogin, 500);
const ownChannels = (ownerFeed?.ownedChannels || []).filter((item) => ( const ownChannels = Array.isArray(ownerFeed?.ownedChannels) ? ownerFeed.ownedChannels : [];
String(item?.channel?.ownerBlockchainName || '') === String(user.blockchainName) const matches = ownChannels.filter((item) => (
));
const match = ownChannels.find((item) => (
String(item?.channel?.channelName || '').trim().toLowerCase() === channelName String(item?.channel?.channelName || '').trim().toLowerCase() === channelName
)); ));
if (!match) { if (!matches.length) {
throw new Error('Канал не найден у указанного автора.'); 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 { return {
ownerBlockchainName: String(match?.channel?.ownerBlockchainName || user.blockchainName), ownerBlockchainName: String(match?.channel?.ownerBlockchainName || user.blockchainName),
rootBlockNumber: Number(match?.channel?.channelRoot?.blockNumber), 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 }) { function openSimpleSubscribeModal({ kind, kindLabel, submitLabel, unfollow = false, onSuccess }) {
const targetHint = kind === 'channel' const targetHint = kind === 'channel'
? '<p class="meta-muted">Канал: user/channel, имя канала или bch:number:hash.</p>' ? '<p class="meta-muted">Канал: user/channel, имя канала или bch:number:hash.</p>'
@ -306,35 +341,62 @@ function openSimpleSubscribeModal({ kind, kindLabel, submitLabel, unfollow = fal
errorEl.textContent = ''; errorEl.textContent = '';
try { try {
let channelTarget = null;
let userTargetLogin = '';
if (kind === 'user') { if (kind === 'user') {
userTargetLogin = normalizeLoginInput(value);
await authService.addBlockFollowUser({ await authService.addBlockFollowUser({
login, login,
targetLogin: normalizeLoginInput(value), targetLogin: userTargetLogin,
storagePwd, storagePwd,
unfollow, unfollow,
}); });
} else if (kind === 'channel') { } else if (kind === 'channel') {
const target = await resolveChannelTargetFromInput(value); channelTarget = await resolveChannelTargetFromInput(value);
if (!target?.ownerBlockchainName || !Number.isFinite(target.rootBlockNumber)) { if (!channelTarget?.ownerBlockchainName || !Number.isFinite(channelTarget.rootBlockNumber)) {
throw new Error('Канал не найден.'); throw new Error('Канал не найден.');
} }
await authService.addBlockFollowChannel({ await authService.addBlockFollowChannel({
login, login,
storagePwd, storagePwd,
targetBlockchainName: target.ownerBlockchainName, targetBlockchainName: channelTarget.ownerBlockchainName,
targetBlockNumber: target.rootBlockNumber, targetBlockNumber: channelTarget.rootBlockNumber,
targetBlockHashHex: target.rootBlockHash, targetBlockHashHex: channelTarget.rootBlockHash,
unfollow, unfollow,
}); });
} else { } else {
throw new Error('Неподдерживаемый тип подписки'); 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); softHaptic(15);
showToast(unfollow ? 'Отписка выполнена' : 'Подписка выполнена'); showToast(unfollow ? 'Отписка выполнена' : 'Подписка выполнена');
close(); close();
if (typeof onSuccess === 'function') onSuccess();
} catch (error) { } catch (error) {
errorEl.textContent = toUserMessage(error, `${submitText} не удалось.`); errorEl.textContent = toUserMessage(error, `${submitText} не удалось.`);
setBusy(false); setBusy(false);

View File

@ -212,12 +212,30 @@ public final class DatabaseTriggersInstaller {
SELECT SELECT
NEW.login, NEW.login,
NEW.msg_sub_type, NEW.msg_sub_type,
COALESCE(
NEW.to_login, 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_bch_name,
NEW.to_block_number, NEW.to_block_number,
NEW.to_block_hash NEW.to_block_hash
WHERE NEW.msg_sub_type IN (%d, %d, %d) 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; AND NEW.to_bch_name IS NOT NULL;
-- 2) если запись есССЊ †обновляем акСуальнСРµ to_* -- 2) если запись есССЊ †обновляем акСуальнСРµ to_*
@ -228,27 +246,64 @@ public final class DatabaseTriggersInstaller {
to_block_hash = NEW.to_block_hash to_block_hash = NEW.to_block_hash
WHERE login = NEW.login WHERE login = NEW.login
AND rel_type = NEW.msg_sub_type AND rel_type = NEW.msg_sub_type
AND to_login = NEW.to_login AND to_login = COALESCE(
AND NEW.msg_sub_type IN (%d, %d, %d) NEW.to_login,
AND NEW.to_login IS NOT NULL 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; AND NEW.to_bch_name IS NOT NULL;
-- UNFRIEND/UNCONTACT/UNFOLLOW: -- UNFRIEND/UNCONTACT/UNFOLLOW:
-- удаляем СЃРѕРѕСРІРµССЃСРІСѓСЋСее "позитивное" СЃРѕСЃСРѕСЏРЅРёРµ -- удаляем СЃРѕРѕСРІРµССЃСРІСѓСЋСее "позитивное" СЃРѕСЃСРѕСЏРЅРёРµ
DELETE FROM connections_state DELETE FROM connections_state
WHERE login = NEW.login 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 AND rel_type = CASE NEW.msg_sub_type
WHEN %d THEN %d WHEN %d THEN %d
WHEN %d THEN %d WHEN %d THEN %d
WHEN %d THEN %d WHEN %d THEN %d
ELSE rel_type ELSE rel_type
END 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); AND NEW.msg_sub_type IN (%d, %d, %d);
END; END;
""".formatted( """.formatted(
FRIEND, CONTACT, FOLLOW, FRIEND, CONTACT, FOLLOW,
FRIEND, CONTACT, FOLLOW, FRIEND, CONTACT,
UNFRIEND, FRIEND, UNFRIEND, FRIEND,
UNCONTACT, CONTACT, UNCONTACT, CONTACT,