mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-02 12:53:10 +00:00
chore: user cannot moderate themselves
This commit is contained in:
parent
f9863121ba
commit
3d3eafd626
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { ArrowUp, ArrowDown, Clock, Shield, UserX } from 'lucide-react';
|
||||
import { ArrowUp, ArrowDown, Clock, MessageSquareX, UserX } from 'lucide-react';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import type { Comment } from '@opchan/core';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@ -174,7 +174,7 @@ const CommentCard: React.FC<CommentCardProps> = ({
|
||||
className="h-6 w-6 text-cyber-neutral hover:text-orange-500"
|
||||
onClick={() => onModerateComment(comment.id)}
|
||||
>
|
||||
<Shield className="h-3 w-3" />
|
||||
<MessageSquareX className="h-3 w-3" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
|
||||
@ -24,7 +24,11 @@ export interface LocalDatabaseCache {
|
||||
posts: PostCache;
|
||||
comments: CommentCache;
|
||||
votes: VoteCache;
|
||||
moderations: { [targetId: string]: ModerateMessage };
|
||||
// Moderations keyed by composite key:
|
||||
// - post: 'post:<postId>'
|
||||
// - comment: 'comment:<commentId>'
|
||||
// - user: '<cellId>:user:<address>'
|
||||
moderations: { [key: string]: (ModerateMessage & { key?: string }) };
|
||||
userIdentities: UserIdentityCache;
|
||||
bookmarks: BookmarkCache;
|
||||
}
|
||||
@ -212,13 +216,20 @@ export class LocalDatabase {
|
||||
}
|
||||
case MessageType.MODERATE: {
|
||||
const modMsg = message as ModerateMessage;
|
||||
if (
|
||||
!this.cache.moderations[modMsg.targetId] ||
|
||||
this.cache.moderations[modMsg.targetId]?.timestamp !==
|
||||
modMsg.timestamp
|
||||
) {
|
||||
this.cache.moderations[modMsg.targetId] = modMsg;
|
||||
this.put(STORE.MODERATIONS, modMsg);
|
||||
// Compose key:
|
||||
// - post: `post:${postId}`
|
||||
// - comment: `comment:${commentId}`
|
||||
// - user: `${cellId}:user:${address}` (per-cell user moderation)
|
||||
const key =
|
||||
modMsg.targetType === 'user'
|
||||
? `${modMsg.cellId}:user:${modMsg.targetId}`
|
||||
: `${modMsg.targetType}:${modMsg.targetId}`;
|
||||
|
||||
const existing = this.cache.moderations[key];
|
||||
if (!existing || modMsg.timestamp > existing.timestamp) {
|
||||
// Store in cache and persist with computed key
|
||||
this.cache.moderations[key] = { ...(modMsg as ModerateMessage), key };
|
||||
this.put(STORE.MODERATIONS, { ...(modMsg as ModerateMessage), key });
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -267,7 +278,7 @@ export class LocalDatabase {
|
||||
PostMessage[],
|
||||
CommentMessage[],
|
||||
(VoteMessage & { key: string })[],
|
||||
ModerateMessage[],
|
||||
(ModerateMessage & { key: string })[],
|
||||
({ address: string } & UserIdentityCache[string])[],
|
||||
Bookmark[],
|
||||
] = await Promise.all([
|
||||
@ -275,7 +286,7 @@ export class LocalDatabase {
|
||||
this.getAllFromStore<PostMessage>(STORE.POSTS),
|
||||
this.getAllFromStore<CommentMessage>(STORE.COMMENTS),
|
||||
this.getAllFromStore<VoteMessage & { key: string }>(STORE.VOTES),
|
||||
this.getAllFromStore<ModerateMessage>(STORE.MODERATIONS),
|
||||
this.getAllFromStore<ModerateMessage & { key: string }>(STORE.MODERATIONS),
|
||||
this.getAllFromStore<{ address: string } & UserIdentityCache[string]>(
|
||||
STORE.USER_IDENTITIES
|
||||
),
|
||||
@ -293,7 +304,7 @@ export class LocalDatabase {
|
||||
})
|
||||
);
|
||||
this.cache.moderations = Object.fromEntries(
|
||||
moderations.map(m => [m.targetId, m])
|
||||
moderations.map(m => [m.key, m])
|
||||
);
|
||||
this.cache.userIdentities = Object.fromEntries(
|
||||
identities.map(u => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export const DB_NAME = 'opchan-local';
|
||||
export const DB_VERSION = 4;
|
||||
export const DB_VERSION = 5;
|
||||
|
||||
export const STORE = {
|
||||
CELLS: 'cells',
|
||||
@ -49,10 +49,12 @@ export function openLocalDB(): Promise<IDBDatabase> {
|
||||
// Votes are keyed by composite key `${targetId}:${author}`
|
||||
db.createObjectStore(STORE.VOTES, { keyPath: 'key' });
|
||||
}
|
||||
if (!db.objectStoreNames.contains(STORE.MODERATIONS)) {
|
||||
// Moderations keyed by targetId
|
||||
db.createObjectStore(STORE.MODERATIONS, { keyPath: 'targetId' });
|
||||
// Moderations store: recreate with composite key support
|
||||
if (db.objectStoreNames.contains(STORE.MODERATIONS)) {
|
||||
db.deleteObjectStore(STORE.MODERATIONS);
|
||||
}
|
||||
// Moderations keyed by computed 'key' (e.g., 'post:postId', 'comment:commentId', 'cellId:user:userAddress')
|
||||
db.createObjectStore(STORE.MODERATIONS, { keyPath: 'key' });
|
||||
if (!db.objectStoreNames.contains(STORE.USER_IDENTITIES)) {
|
||||
// User identities keyed by address
|
||||
db.createObjectStore(STORE.USER_IDENTITIES, { keyPath: 'address' });
|
||||
|
||||
@ -431,6 +431,7 @@ export class ForumActions {
|
||||
currentUser,
|
||||
isAuthenticated,
|
||||
cellOwner,
|
||||
commentAuthor,
|
||||
} = params;
|
||||
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
@ -446,6 +447,12 @@ export class ForumActions {
|
||||
error: 'Not authorized. Only the cell admin can moderate comments.',
|
||||
};
|
||||
}
|
||||
if (currentUser.address === commentAuthor) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'You cannot moderate your own comments.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const unsignedMod: UnsignedModerateMessage = {
|
||||
@ -520,6 +527,12 @@ export class ForumActions {
|
||||
error: 'Not authorized. Only the cell admin can moderate users.',
|
||||
};
|
||||
}
|
||||
if (currentUser.address === userAddress) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'You cannot moderate yourself.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const unsignedMod: UnsignedModerateMessage = {
|
||||
@ -647,6 +660,7 @@ export class ForumActions {
|
||||
currentUser,
|
||||
isAuthenticated,
|
||||
cellOwner,
|
||||
commentAuthor,
|
||||
} = params;
|
||||
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
@ -662,6 +676,12 @@ export class ForumActions {
|
||||
error: 'Not authorized. Only the cell admin can unmoderate comments.',
|
||||
};
|
||||
}
|
||||
if (currentUser.address === commentAuthor) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'You cannot unmoderate your own comments.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const unsignedMod: UnsignedModerateMessage = {
|
||||
@ -736,6 +756,12 @@ export class ForumActions {
|
||||
error: 'Not authorized. Only the cell admin can unmoderate users.',
|
||||
};
|
||||
}
|
||||
if (currentUser.address === userAddress) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'You cannot unmoderate yourself.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const unsignedMod: UnsignedModerateMessage = {
|
||||
@ -826,6 +852,7 @@ interface CommentModerationParams extends BaseActionParams {
|
||||
commentId: string;
|
||||
reason?: string;
|
||||
cellOwner: string;
|
||||
commentAuthor: string;
|
||||
}
|
||||
|
||||
interface UserModerationParams extends BaseActionParams {
|
||||
|
||||
@ -18,27 +18,11 @@ export const transformCell = async (
|
||||
userVerificationStatus?: UserVerificationStatus,
|
||||
posts?: Post[]
|
||||
): Promise<Cell | null> => {
|
||||
// Message validity already enforced upstream
|
||||
|
||||
const transformedCell: Cell = {
|
||||
id: cellMessage.id,
|
||||
type: cellMessage.type,
|
||||
author: cellMessage.author,
|
||||
name: cellMessage.name,
|
||||
description: cellMessage.description,
|
||||
icon: cellMessage.icon || '',
|
||||
timestamp: cellMessage.timestamp,
|
||||
signature: cellMessage.signature,
|
||||
browserPubKey: cellMessage.browserPubKey,
|
||||
delegationProof: cellMessage.delegationProof,
|
||||
};
|
||||
|
||||
// Calculate relevance score if user verification status and posts are provided
|
||||
if (userVerificationStatus && posts) {
|
||||
const relevanceCalculator = new RelevanceCalculator();
|
||||
|
||||
const relevanceResult = relevanceCalculator.calculateCellScore(
|
||||
transformedCell,
|
||||
cellMessage,
|
||||
posts
|
||||
);
|
||||
|
||||
@ -50,14 +34,14 @@ export const transformCell = async (
|
||||
});
|
||||
|
||||
return {
|
||||
...transformedCell,
|
||||
...cellMessage,
|
||||
relevanceScore: relevanceResult.score,
|
||||
activeMemberCount: activeMembers.size,
|
||||
relevanceDetails: relevanceResult.details,
|
||||
};
|
||||
}
|
||||
|
||||
return transformedCell;
|
||||
return cellMessage;
|
||||
};
|
||||
|
||||
export const transformPost = async (
|
||||
@ -97,17 +81,8 @@ export const transformPost = async (
|
||||
!!userModMsg && userModMsg.action === EModerationAction.MODERATE;
|
||||
|
||||
const transformedPost: Post = {
|
||||
id: postMessage.id,
|
||||
type: postMessage.type,
|
||||
author: postMessage.author,
|
||||
cellId: postMessage.cellId,
|
||||
authorAddress: postMessage.author,
|
||||
title: postMessage.title,
|
||||
content: postMessage.content,
|
||||
timestamp: postMessage.timestamp,
|
||||
signature: postMessage.signature,
|
||||
browserPubKey: postMessage.browserPubKey,
|
||||
delegationProof: postMessage.delegationProof,
|
||||
...postMessage,
|
||||
upvotes,
|
||||
downvotes,
|
||||
moderated: isPostModerated || isUserModerated,
|
||||
@ -223,16 +198,8 @@ export const transformComment = async (
|
||||
!!userModMsg && userModMsg.action === EModerationAction.MODERATE;
|
||||
|
||||
const transformedComment: Comment = {
|
||||
id: commentMessage.id,
|
||||
type: commentMessage.type,
|
||||
author: commentMessage.author,
|
||||
postId: commentMessage.postId,
|
||||
...commentMessage,
|
||||
authorAddress: commentMessage.author,
|
||||
content: commentMessage.content,
|
||||
timestamp: commentMessage.timestamp,
|
||||
signature: commentMessage.signature,
|
||||
browserPubKey: commentMessage.browserPubKey,
|
||||
delegationProof: commentMessage.delegationProof,
|
||||
upvotes,
|
||||
downvotes,
|
||||
moderated: isCommentModerated || isUserModerated,
|
||||
|
||||
@ -193,8 +193,9 @@ export function useContent() {
|
||||
const currentUser = session.currentUser;
|
||||
const isAuthenticated = Boolean(currentUser);
|
||||
const cell = content.cells.find(c => c.id === cellId);
|
||||
const comment = content.comments.find(c => c.id === commentId);
|
||||
const res = await client.forumActions.moderateComment(
|
||||
{ cellId, commentId, reason, currentUser, isAuthenticated, cellOwner: cell?.author ?? '' },
|
||||
{ cellId, commentId, reason, currentUser, isAuthenticated, cellOwner: cell?.author ?? '', commentAuthor: comment?.author ?? '' },
|
||||
() => reflectCache(client)
|
||||
);
|
||||
reflectCache(client);
|
||||
@ -204,8 +205,9 @@ export function useContent() {
|
||||
const currentUser = session.currentUser;
|
||||
const isAuthenticated = Boolean(currentUser);
|
||||
const cell = content.cells.find(c => c.id === cellId);
|
||||
const comment = content.comments.find(c => c.id === commentId);
|
||||
const res = await client.forumActions.unmoderateComment(
|
||||
{ cellId, commentId, reason, currentUser, isAuthenticated, cellOwner: cell?.author ?? '' },
|
||||
{ cellId, commentId, reason, currentUser, isAuthenticated, cellOwner: cell?.author ?? '', commentAuthor: comment?.author ?? '' },
|
||||
() => reflectCache(client)
|
||||
);
|
||||
reflectCache(client);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user