fix: moderation

This commit is contained in:
Danish Arora 2025-09-05 16:50:30 +05:30
parent d2a512211f
commit cad1dcb5b4
No known key found for this signature in database
GPG Key ID: 1C6EF37CDAE1426E
9 changed files with 116 additions and 42 deletions

View File

@ -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 = () => {
</div>
<p className="text-sm break-words">{comment.content}</p>
{canModerate(cell?.id || '') && !comment.moderated && (
<Button
size="sm"
variant="destructive"
className="ml-2"
onClick={() => handleModerateComment(comment.id)}
>
Moderate
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon"
variant="ghost"
className="h-6 w-6 text-cyber-neutral hover:text-orange-500"
onClick={() => handleModerateComment(comment.id)}
>
<Shield className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Moderate comment</p>
</TooltipContent>
</Tooltip>
)}
{post.cell &&
canModerate(post.cell.id) &&
comment.author !== post.author && (
<Button
size="sm"
variant="destructive"
className="ml-2"
onClick={() => handleModerateUser(comment.author)}
>
Moderate User
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon"
variant="ghost"
className="h-6 w-6 text-cyber-neutral hover:text-red-500"
onClick={() => handleModerateUser(comment.author)}
>
<UserX className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Moderate user</p>
</TooltipContent>
</Tooltip>
)}
{comment.moderated && (
<span className="ml-2 text-xs text-red-500">

View File

@ -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 = () => {
</div>
</Link>
{canModerate(cell.id) && !post.moderated && (
<Button
size="sm"
variant="destructive"
className="ml-2"
onClick={() => handleModerate(post.id)}
>
Moderate
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon"
variant="ghost"
className="h-6 w-6 text-cyber-neutral hover:text-orange-500"
onClick={() => handleModerate(post.id)}
>
<Shield className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Moderate post</p>
</TooltipContent>
</Tooltip>
)}
{canModerate(cell.id) && post.author !== cell.signature && (
<Button
size="sm"
variant="destructive"
className="ml-2"
onClick={() => handleModerateUser(post.author)}
>
Moderate User
</Button>
{canModerate(cell.id) && post.author !== cell.author && (
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon"
variant="ghost"
className="h-6 w-6 text-cyber-neutral hover:text-red-500"
onClick={() => handleModerateUser(post.author)}
>
<UserX className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Moderate user</p>
</TooltipContent>
</Tooltip>
)}
{post.moderated && (
<span className="ml-2 text-xs text-red-500">

View File

@ -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({

View File

@ -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(

View File

@ -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,

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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
}
/**

View File

@ -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
}
/**