From cad1dcb5b4eef514d5d02d141c97acf6784ac22b Mon Sep 17 00:00:00 2001 From: Danish Arora Date: Fri, 5 Sep 2025 16:50:30 +0530 Subject: [PATCH] fix: moderation --- src/components/PostDetail.tsx | 53 +++++++++++++++------- src/components/PostList.tsx | 55 ++++++++++++++++------- src/hooks/actions/useForumActions.ts | 12 ++--- src/lib/forum/__tests__/relevance.test.ts | 19 ++++++++ src/lib/forum/transformers.ts | 9 +++- src/lib/services/UserIdentityService.ts | 1 + src/lib/waku/CodecManager.ts | 5 ++- src/types/forum.ts | 2 +- src/types/waku.ts | 2 + 9 files changed, 116 insertions(+), 42 deletions(-) diff --git a/src/components/PostDetail.tsx b/src/components/PostDetail.tsx index 17e6083..aa9b501 100644 --- a/src/components/PostDetail.tsx +++ b/src/components/PostDetail.tsx @@ -17,12 +17,19 @@ import { MessageCircle, Send, Loader2, + Shield, + UserX, } from 'lucide-react'; import { formatDistanceToNow } from 'date-fns'; import { RelevanceIndicator } from './ui/relevance-indicator'; import { AuthorDisplay } from './ui/author-display'; import { usePending, usePendingVote } from '@/hooks/usePending'; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip'; // Extracted child component to respect Rules of Hooks const PendingBadge: React.FC<{ id: string }> = ({ id }) => { @@ -358,26 +365,40 @@ const PostDetail = () => {

{comment.content}

{canModerate(cell?.id || '') && !comment.moderated && ( - + + + + + +

Moderate comment

+
+
)} {post.cell && canModerate(post.cell.id) && comment.author !== post.author && ( - + + + + + +

Moderate user

+
+
)} {comment.moderated && ( diff --git a/src/components/PostList.tsx b/src/components/PostList.tsx index 6edca69..9971dc3 100644 --- a/src/components/PostList.tsx +++ b/src/components/PostList.tsx @@ -21,11 +21,18 @@ import { ArrowDown, RefreshCw, Eye, + Shield, + UserX, } from 'lucide-react'; import { formatDistanceToNow } from 'date-fns'; import { CypherImage } from './ui/CypherImage'; import { Badge } from '@/components/ui/badge'; import { AuthorDisplay } from './ui/author-display'; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip'; const PostList = () => { const { cellId } = useParams<{ cellId: string }>(); @@ -312,24 +319,38 @@ const PostList = () => { {canModerate(cell.id) && !post.moderated && ( - + + + + + +

Moderate post

+
+
)} - {canModerate(cell.id) && post.author !== cell.signature && ( - + {canModerate(cell.id) && post.author !== cell.author && ( + + + + + +

Moderate user

+
+
)} {post.moderated && ( diff --git a/src/hooks/actions/useForumActions.ts b/src/hooks/actions/useForumActions.ts index 3dceb21..0139d60 100644 --- a/src/hooks/actions/useForumActions.ts +++ b/src/hooks/actions/useForumActions.ts @@ -291,7 +291,7 @@ export function useForumActions(): ForumActions { const canModerate = permissions.canModerate(cellId) && cell && - currentUser?.address === cell.signature; + currentUser?.address === cell.author; if (!canModerate) { toast({ @@ -307,7 +307,7 @@ export function useForumActions(): ForumActions { cellId, postId, reason, - cell.signature + cell.author ); if (result) { toast({ @@ -339,7 +339,7 @@ export function useForumActions(): ForumActions { const canModerate = permissions.canModerate(cellId) && cell && - currentUser?.address === cell.signature; + currentUser?.address === cell.author; if (!canModerate) { toast({ @@ -355,7 +355,7 @@ export function useForumActions(): ForumActions { cellId, commentId, reason, - cell.signature + cell.author ); if (result) { toast({ @@ -387,7 +387,7 @@ export function useForumActions(): ForumActions { const canModerate = permissions.canModerate(cellId) && cell && - currentUser?.address === cell.signature; + currentUser?.address === cell.author; if (!canModerate) { toast({ @@ -412,7 +412,7 @@ export function useForumActions(): ForumActions { cellId, userAddress, reason, - cell.signature + cell.author ); if (result) { toast({ diff --git a/src/lib/forum/__tests__/relevance.test.ts b/src/lib/forum/__tests__/relevance.test.ts index 7e8dcfb..0c188a6 100644 --- a/src/lib/forum/__tests__/relevance.test.ts +++ b/src/lib/forum/__tests__/relevance.test.ts @@ -6,8 +6,18 @@ import { EDisplayPreference, } from '@/types/identity'; import { VoteMessage, MessageType } from '@/types/waku'; +import { DelegationProof } from '@/lib/delegation/types'; import { expect, describe, beforeEach, it } from 'vitest'; +// Mock delegation proof for tests +const mockDelegationProof: DelegationProof = { + authMessage: 'I authorize browser key: test-key until 9999999999', + walletSignature: 'mock-signature', + expiryTimestamp: 9999999999, + walletAddress: 'test-address', + walletType: 'ethereum', +}; + describe('RelevanceCalculator', () => { let calculator: RelevanceCalculator; let mockUserVerificationStatus: UserVerificationStatus; @@ -36,6 +46,7 @@ describe('RelevanceCalculator', () => { downvotes: [], signature: 'test', browserPubKey: 'test', + delegationProof: mockDelegationProof, }; const result = calculator.calculatePostScore( @@ -64,6 +75,7 @@ describe('RelevanceCalculator', () => { downvotes: [], signature: 'test', browserPubKey: 'test', + delegationProof: mockDelegationProof, }; const result = calculator.calculatePostScore( @@ -141,6 +153,7 @@ describe('RelevanceCalculator', () => { moderated: true, signature: 'test', browserPubKey: 'test', + delegationProof: mockDelegationProof, }; const result = calculator.calculatePostScore( @@ -168,6 +181,7 @@ describe('RelevanceCalculator', () => { downvotes: [], signature: 'test', browserPubKey: 'test', + delegationProof: mockDelegationProof, }; const votes: VoteMessage[] = [ @@ -180,6 +194,7 @@ describe('RelevanceCalculator', () => { type: MessageType.VOTE, signature: 'test', browserPubKey: 'test', + delegationProof: mockDelegationProof, }, { id: 'vote2', @@ -190,6 +205,7 @@ describe('RelevanceCalculator', () => { type: MessageType.VOTE, signature: 'test', browserPubKey: 'test', + delegationProof: mockDelegationProof, }, ]; @@ -206,6 +222,7 @@ describe('RelevanceCalculator', () => { author: 'user1', signature: 'test', browserPubKey: 'test', + delegationProof: mockDelegationProof, }, ]; @@ -241,6 +258,7 @@ describe('RelevanceCalculator', () => { downvotes: [], signature: 'test', browserPubKey: 'test', + delegationProof: mockDelegationProof, }; const oldPost: Post = { @@ -256,6 +274,7 @@ describe('RelevanceCalculator', () => { downvotes: [], signature: 'test', browserPubKey: 'test', + delegationProof: mockDelegationProof, }; const recentResult = calculator.calculatePostScore( diff --git a/src/lib/forum/transformers.ts b/src/lib/forum/transformers.ts index 9fbcca8..e4b5bb9 100644 --- a/src/lib/forum/transformers.ts +++ b/src/lib/forum/transformers.ts @@ -29,6 +29,7 @@ export const transformCell = async ( timestamp: cellMessage.timestamp, signature: cellMessage.signature, browserPubKey: cellMessage.browserPubKey, + delegationProof: cellMessage.delegationProof, }; // Calculate relevance score if user verification status and posts are provided @@ -100,6 +101,7 @@ export const transformPost = async ( timestamp: postMessage.timestamp, signature: postMessage.signature, browserPubKey: postMessage.browserPubKey, + delegationProof: postMessage.delegationProof, upvotes, downvotes, moderated: isPostModerated || isUserModerated, @@ -193,12 +195,16 @@ export const transformComment = async ( const modMsg = messageManager.messageCache.moderations[commentMessage.id]; const isCommentModerated = !!modMsg && modMsg.targetType === 'comment'; + // Find the post to get the correct cell ID + const parentPost = Object.values(messageManager.messageCache.posts).find( + post => post.id === commentMessage.postId + ); const userModMsg = Object.values( messageManager.messageCache.moderations ).find( m => m.targetType === 'user' && - m.cellId === commentMessage.postId.split('-')[0] && + m.cellId === parentPost?.cellId && m.targetId === commentMessage.author ); const isUserModerated = !!userModMsg; @@ -213,6 +219,7 @@ export const transformComment = async ( timestamp: commentMessage.timestamp, signature: commentMessage.signature, browserPubKey: commentMessage.browserPubKey, + delegationProof: commentMessage.delegationProof, upvotes, downvotes, moderated: isCommentModerated || isUserModerated, diff --git a/src/lib/services/UserIdentityService.ts b/src/lib/services/UserIdentityService.ts index a8b6be5..d477c87 100644 --- a/src/lib/services/UserIdentityService.ts +++ b/src/lib/services/UserIdentityService.ts @@ -239,6 +239,7 @@ export class UserIdentityService { displayPreference, signature: signedMessage.signature, browserPubKey: signedMessage.browserPubKey, + delegationProof: signedMessage.delegationProof, }; if (callSign && callSign.trim()) { profileMessage.callSign = callSign.trim(); diff --git a/src/lib/waku/CodecManager.ts b/src/lib/waku/CodecManager.ts index 06f9a74..07cab69 100644 --- a/src/lib/waku/CodecManager.ts +++ b/src/lib/waku/CodecManager.ts @@ -5,6 +5,7 @@ import { PostMessage, CommentMessage, VoteMessage, + ModerateMessage, } from '../../types/waku'; import { CONTENT_TOPIC } from './constants'; import { OpchanMessage } from '@/types/forum'; @@ -42,10 +43,12 @@ export class CodecManager { return message as CommentMessage; case MessageType.VOTE: return message as VoteMessage; + case MessageType.MODERATE: + return message as ModerateMessage; case MessageType.USER_PROFILE_UPDATE: return message as UserProfileUpdateMessage; default: - throw new Error(`Unknown message type: ${message}`); + throw new Error(`Unknown message type: `, message); } } diff --git a/src/types/forum.ts b/src/types/forum.ts index d87a0af..fe13636 100644 --- a/src/types/forum.ts +++ b/src/types/forum.ts @@ -111,7 +111,7 @@ export interface Comment extends CommentMessage { export interface SignedMessage { signature: string; browserPubKey: string; - delegationProof?: DelegationProof; // Cryptographic proof that browser key was authorized + delegationProof: DelegationProof; // Cryptographic proof that browser key was authorized - REQUIRED } /** diff --git a/src/types/waku.ts b/src/types/waku.ts index 3d712f4..2533ace 100644 --- a/src/types/waku.ts +++ b/src/types/waku.ts @@ -1,4 +1,5 @@ import { EDisplayPreference, EVerificationStatus } from './identity'; +import { DelegationProof } from '@/lib/delegation/types'; /** * Message types for Waku communication @@ -28,6 +29,7 @@ export interface UnsignedBaseMessage { export interface BaseMessage extends UnsignedBaseMessage { signature: string; // Message signature for verification browserPubKey: string; // Public key that signed the message + delegationProof: DelegationProof; // Cryptographic proof that browser key was authorized } /**