From ad091d2be83031fdc956053c1efa0cc35bbb9260 Mon Sep 17 00:00:00 2001 From: Tommaso Casaburi Date: Sat, 4 Jul 2026 23:48:19 +0700 Subject: [PATCH] fix(post transfer): publish trash with public key --- .../__tests__/post-transfer-modal.test.tsx | 30 +++++++++++++++++ .../post-transfer-modal.tsx | 12 +++++-- src/lib/__tests__/comment-transfer.test.ts | 33 ++++++++++++++++++- src/lib/comment-transfer.ts | 32 ++++++++++++++++-- src/lib/special-boards.ts | 6 ++-- 5 files changed, 105 insertions(+), 8 deletions(-) diff --git a/src/components/post-transfer-modal/__tests__/post-transfer-modal.test.tsx b/src/components/post-transfer-modal/__tests__/post-transfer-modal.test.tsx index 20107f84..0fc74822 100644 --- a/src/components/post-transfer-modal/__tests__/post-transfer-modal.test.tsx +++ b/src/components/post-transfer-modal/__tests__/post-transfer-modal.test.tsx @@ -165,6 +165,31 @@ describe('PostTransferModal', () => { expect(options).toContainEqual({ text: TRASH_BOARD_TITLE, value: TRASH_BOARD_ADDRESS }); }); + it('publishes hidden trash board transfers with the board public key identity', async () => { + await renderTransferModal(); + + const select = document.body.querySelector('select'); + expect(select).not.toBeNull(); + await act(async () => { + select!.value = TRASH_BOARD_ADDRESS; + select!.dispatchEvent(new Event('change', { bubbles: true })); + }); + + const form = document.body.querySelector('form'); + expect(form).not.toBeNull(); + await act(async () => { + form!.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true })); + await Promise.resolve(); + }); + + const [publishOptions] = testState.publishCommentMock.mock.calls[0] as [Record, string]; + expect(publishOptions).toMatchObject({ + communityAddress: TRASH_BOARD_PUBLIC_KEY, + communityPublicKey: TRASH_BOARD_PUBLIC_KEY, + }); + expect(publishOptions).not.toHaveProperty('communityName'); + }); + it('resolves the hidden trash board when it is the transfer source', async () => { await renderTransferModal({ ...baseComment, communityAddress: TRASH_BOARD_ADDRESS }); @@ -192,6 +217,11 @@ describe('PostTransferModal', () => { }); const [sourceModerationOptions] = testState.publishCommentModerationMock.mock.calls[1] as [{ commentModeration: { reason: string } }, string]; + expect(sourceModerationOptions).toMatchObject({ + communityAddress: TRASH_BOARD_PUBLIC_KEY, + communityPublicKey: TRASH_BOARD_PUBLIC_KEY, + }); + expect(sourceModerationOptions).not.toHaveProperty('communityName'); expect(sourceModerationOptions.commentModeration.reason).toContain('/trash/'); expect(sourceModerationOptions.commentModeration.reason).toContain('(the rules)'); }); diff --git a/src/components/post-transfer-modal/post-transfer-modal.tsx b/src/components/post-transfer-modal/post-transfer-modal.tsx index f6d4f1cc..47a21762 100644 --- a/src/components/post-transfer-modal/post-transfer-modal.tsx +++ b/src/components/post-transfer-modal/post-transfer-modal.tsx @@ -19,6 +19,7 @@ import { getInitialTransferFields, getTargetTransferModerationFlairs, getTransferBoardReference, + getTransferPublishIdentity, getTransferPublishPayload, getTransferSourceBoardReference, getTransferSourceBoardRulesLink, @@ -344,7 +345,9 @@ const PostTransferModal = ({ comment, onClose, onTransferStateChange, onTransfer try { await createAccount(temporaryAccountName); temporaryAccountCreated = true; - const payload = getTransferPublishPayload(comment, selectedFields, resolvedTargetBoardAddress); + const targetPublishIdentity = getTransferPublishIdentity(resolvedTargetBoard, resolvedTargetBoardAddress); + const sourcePublishIdentity = sourceCommunityAddress ? getTransferPublishIdentity(sourceBoard, sourceCommunityAddress) : undefined; + const payload = getTransferPublishPayload(comment, selectedFields, resolvedTargetBoardAddress, resolvedTargetBoard); await publishComment( { ...payload, @@ -371,11 +374,14 @@ const PostTransferModal = ({ comment, onClose, onTransferStateChange, onTransfer if (!targetCommentCid) { throw new Error('Transferred post was accepted, but no target CID was returned.'); } + if (!sourcePublishIdentity) { + throw new Error('Transferred post was accepted, but no source board was resolved.'); + } // Queue the target marker first so a target moderation failure does not remove the original post. await publishCommentModeration({ commentCid: targetCommentCid, - communityAddress: resolvedTargetBoardAddress, + ...targetPublishIdentity, commentModeration: { flairs: getTargetTransferModerationFlairs(comment, selectedFields), }, @@ -383,7 +389,7 @@ const PostTransferModal = ({ comment, onClose, onTransferStateChange, onTransfer }); await publishCommentModeration({ commentCid: sourceCommentCid, - communityAddress: sourceCommunityAddress, + ...sourcePublishIdentity, commentModeration: getTransferSourceModeration( comment, getTransferBoardReference(resolvedTargetBoard, resolvedTargetBoardAddress), diff --git a/src/lib/__tests__/comment-transfer.test.ts b/src/lib/__tests__/comment-transfer.test.ts index 90a0fb6a..9fb19109 100644 --- a/src/lib/__tests__/comment-transfer.test.ts +++ b/src/lib/__tests__/comment-transfer.test.ts @@ -3,13 +3,14 @@ import { canTransferComment, getTargetTransferModerationFlairs, getTransferPublishPayload, + getTransferPublishIdentity, getTransferSourceBoardReference, getTransferSourceBoardRulesLink, getTransferSourceModeration, hasTransferredCommentMarker, TRANSFERRED_COMMENT_FLAIR, } from '../comment-transfer'; -import { TRASH_BOARD_ADDRESS, TRASH_BOARD_CODE, TRASH_BOARD_TITLE } from '../special-boards'; +import { TRASH_BOARD_ADDRESS, TRASH_BOARD_CODE, TRASH_BOARD_PUBLIC_KEY, TRASH_BOARD_TITLE } from '../special-boards'; describe('comment-transfer', () => { const selectedFields = { @@ -58,6 +59,36 @@ describe('comment-transfer', () => { }); }); + it('uses the public key identity when publishing transfers to a special board', () => { + expect(getTransferPublishIdentity({ address: 'tech-posting.eth', directoryCode: 'g' }, 'tech-posting.eth')).toEqual({ + communityAddress: 'tech-posting.eth', + }); + expect(getTransferPublishIdentity({ address: TRASH_BOARD_ADDRESS, directoryCode: TRASH_BOARD_CODE, publicKey: TRASH_BOARD_PUBLIC_KEY }, TRASH_BOARD_ADDRESS)).toEqual( + { + communityAddress: TRASH_BOARD_PUBLIC_KEY, + communityPublicKey: TRASH_BOARD_PUBLIC_KEY, + }, + ); + + const payload = getTransferPublishPayload( + { + cid: 'source', + communityAddress: 'music-posting.eth', + content: 'wrong board', + } as never, + selectedFields, + TRASH_BOARD_ADDRESS, + { address: TRASH_BOARD_ADDRESS, directoryCode: TRASH_BOARD_CODE, publicKey: TRASH_BOARD_PUBLIC_KEY }, + ); + + expect(payload).toMatchObject({ + communityAddress: TRASH_BOARD_PUBLIC_KEY, + communityPublicKey: TRASH_BOARD_PUBLIC_KEY, + content: 'wrong board', + }); + expect(payload).not.toHaveProperty('communityName'); + }); + it('copies selected comment body text verbatim while still ignoring blank bodies', () => { expect( getTransferPublishPayload( diff --git a/src/lib/comment-transfer.ts b/src/lib/comment-transfer.ts index 281430c5..190ffc87 100644 --- a/src/lib/comment-transfer.ts +++ b/src/lib/comment-transfer.ts @@ -10,9 +10,16 @@ export type PostTransferFields = Record; export type TransferBoardLike = { address?: string; directoryCode?: string; + publicKey?: string; title?: string; }; +export type TransferPublishIdentity = { + communityAddress: string; + communityPublicKey?: string; + communityName?: string; +}; + export const TRANSFERRED_COMMENT_FLAIR_TEXT = '5chan:transferred'; export const TRANSFERRED_COMMENT_FLAIR = { text: TRANSFERRED_COMMENT_FLAIR_TEXT } as const; export const TRANSFER_FIELD_KEYS: PostTransferField[] = ['displayName', 'title', 'content', 'link', 'spoiler', 'flairs']; @@ -63,9 +70,30 @@ export const getAvailableTransferFields = (comment: Comment): PostTransferField[ export const hasSelectedTransferFields = (fields: PostTransferFields, availableFields: PostTransferField[]) => availableFields.some((field) => fields[field]); -export const getTransferPublishPayload = (comment: Comment, fields: PostTransferFields, targetBoardAddress: string): Record => { +export const getTransferPublishIdentity = (targetBoard: TransferBoardLike | undefined, targetBoardAddress: string): TransferPublishIdentity => { + const boardAddress = getTextField(targetBoard?.address) ?? targetBoardAddress; + const publicKey = getTextField(targetBoard?.publicKey); + + if (publicKey && isSpecialBoardCode(targetBoard?.directoryCode)) { + return { + communityAddress: publicKey, + communityPublicKey: publicKey, + }; + } + + return { + communityAddress: boardAddress, + }; +}; + +export const getTransferPublishPayload = ( + comment: Comment, + fields: PostTransferFields, + targetBoardAddress: string, + targetBoard?: TransferBoardLike, +): Record => { const payload: Record = { - communityAddress: targetBoardAddress, + ...getTransferPublishIdentity(targetBoard, targetBoardAddress), }; const displayName = getCommentDisplayName(comment); const title = getTextField(comment.title); diff --git a/src/lib/special-boards.ts b/src/lib/special-boards.ts index c85cfab7..2e48d7b0 100644 --- a/src/lib/special-boards.ts +++ b/src/lib/special-boards.ts @@ -3,6 +3,7 @@ export interface SpecialBoard { aliases?: string[]; directoryCode: string; nsfw?: boolean; + publicKey?: string; title: string; } @@ -14,14 +15,15 @@ export const TRASH_BOARD_TITLE = '/trash/ - Off-topic'; export const SPECIAL_BOARDS: SpecialBoard[] = [ { address: TRASH_BOARD_ADDRESS, - aliases: [TRASH_BOARD_PUBLIC_KEY, 'off-topic.eth'], + aliases: ['off-topic.eth'], directoryCode: TRASH_BOARD_CODE, nsfw: true, + publicKey: TRASH_BOARD_PUBLIC_KEY, title: TRASH_BOARD_TITLE, }, ]; -const getSpecialBoardLookupAddresses = (board: SpecialBoard): string[] => [board.address, ...(board.aliases ?? [])]; +const getSpecialBoardLookupAddresses = (board: SpecialBoard): string[] => [board.address, ...(board.publicKey ? [board.publicKey] : []), ...(board.aliases ?? [])]; export const getSpecialBoardByCode = (code: string | undefined): SpecialBoard | undefined => code ? SPECIAL_BOARDS.find((board) => board.directoryCode === code) : undefined;