mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-02 12:53:10 +00:00
chore: convert forum actions into a class
This commit is contained in:
parent
34279ff8b1
commit
635e3eb80a
@ -20,7 +20,7 @@ import {
|
||||
} from '@/components/ui/select';
|
||||
import { CypherImage } from './ui/CypherImage';
|
||||
import { RelevanceIndicator } from './ui/relevance-indicator';
|
||||
import { sortCells, SortOption } from '@/lib/forum/sorting';
|
||||
import { sortCells, SortOption } from '@/lib/utils/sorting';
|
||||
|
||||
const CellList = () => {
|
||||
const { cells, isInitialLoading, posts, refreshData, isRefreshing } =
|
||||
|
||||
@ -9,15 +9,7 @@ import { Cell, Post, Comment, OpchanMessage } from '@/types/forum';
|
||||
import { User, EVerificationStatus, DisplayPreference } from '@/types/identity';
|
||||
import { useToast } from '@/components/ui/use-toast';
|
||||
import { useAuth } from '@/contexts/useAuth';
|
||||
import {
|
||||
createPost,
|
||||
createComment,
|
||||
vote,
|
||||
createCell,
|
||||
moderatePost,
|
||||
moderateComment,
|
||||
moderateUser,
|
||||
} from '@/lib/forum/actions';
|
||||
import { ForumActions } from '@/lib/forum/ForumActions';
|
||||
import {
|
||||
setupPeriodicQueries,
|
||||
monitorNetworkHealth,
|
||||
@ -25,7 +17,7 @@ import {
|
||||
} from '@/lib/waku/network';
|
||||
import messageManager from '@/lib/waku';
|
||||
import { getDataFromCache } from '@/lib/forum/transformers';
|
||||
import { RelevanceCalculator } from '@/lib/forum/relevance';
|
||||
import { RelevanceCalculator } from '@/lib/forum/RelevanceCalculator';
|
||||
import { UserVerificationStatus } from '@/types/forum';
|
||||
import { CryptoService } from '@/lib/services';
|
||||
import { getEnsName } from '@wagmi/core';
|
||||
@ -107,6 +99,10 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
const { currentUser, isAuthenticated } = useAuth();
|
||||
|
||||
const cryptoService = useMemo(() => new CryptoService(), []);
|
||||
const forumActions = useMemo(
|
||||
() => new ForumActions(cryptoService),
|
||||
[cryptoService]
|
||||
);
|
||||
|
||||
// Transform message cache data to the expected types
|
||||
const updateStateFromCache = useCallback(() => {
|
||||
@ -289,7 +285,7 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
description: 'Sending your post to the network...',
|
||||
});
|
||||
|
||||
const result = await createPost(
|
||||
const result = await forumActions.createPost(
|
||||
{ cellId, title, content, currentUser, isAuthenticated },
|
||||
updateStateFromCache
|
||||
);
|
||||
@ -322,11 +318,8 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
description: 'Sending your comment to the network...',
|
||||
});
|
||||
|
||||
const result = await createComment(
|
||||
postId,
|
||||
content,
|
||||
currentUser,
|
||||
isAuthenticated,
|
||||
const result = await forumActions.createComment(
|
||||
{ postId, content, currentUser, isAuthenticated },
|
||||
updateStateFromCache
|
||||
);
|
||||
|
||||
@ -359,11 +352,8 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
description: 'Recording your vote on the network...',
|
||||
});
|
||||
|
||||
const result = await vote(
|
||||
postId,
|
||||
isUpvote,
|
||||
currentUser,
|
||||
isAuthenticated,
|
||||
const result = await forumActions.vote(
|
||||
{ targetId: postId, isUpvote, currentUser, isAuthenticated },
|
||||
updateStateFromCache
|
||||
);
|
||||
|
||||
@ -397,11 +387,8 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
description: 'Recording your vote on the network...',
|
||||
});
|
||||
|
||||
const result = await vote(
|
||||
commentId,
|
||||
isUpvote,
|
||||
currentUser,
|
||||
isAuthenticated,
|
||||
const result = await forumActions.vote(
|
||||
{ targetId: commentId, isUpvote, currentUser, isAuthenticated },
|
||||
updateStateFromCache
|
||||
);
|
||||
|
||||
@ -435,12 +422,8 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
description: 'Sending your cell to the network...',
|
||||
});
|
||||
|
||||
const result = await createCell(
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
currentUser,
|
||||
isAuthenticated,
|
||||
const result = await forumActions.createCell(
|
||||
{ name, description, icon, currentUser, isAuthenticated },
|
||||
updateStateFromCache
|
||||
);
|
||||
|
||||
@ -473,13 +456,8 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
description: 'Sending moderation message to the network...',
|
||||
});
|
||||
|
||||
const result = await moderatePost(
|
||||
cellId,
|
||||
postId,
|
||||
reason,
|
||||
currentUser,
|
||||
isAuthenticated,
|
||||
cellOwner,
|
||||
const result = await forumActions.moderatePost(
|
||||
{ cellId, postId, reason, currentUser, isAuthenticated, cellOwner },
|
||||
updateStateFromCache
|
||||
);
|
||||
|
||||
@ -511,13 +489,8 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
description: 'Sending moderation message to the network...',
|
||||
});
|
||||
|
||||
const result = await moderateComment(
|
||||
cellId,
|
||||
commentId,
|
||||
reason,
|
||||
currentUser,
|
||||
isAuthenticated,
|
||||
cellOwner,
|
||||
const result = await forumActions.moderateComment(
|
||||
{ cellId, commentId, reason, currentUser, isAuthenticated, cellOwner },
|
||||
updateStateFromCache
|
||||
);
|
||||
|
||||
@ -544,13 +517,8 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
reason: string | undefined,
|
||||
cellOwner: string
|
||||
) => {
|
||||
const result = await moderateUser(
|
||||
cellId,
|
||||
userAddress,
|
||||
reason,
|
||||
currentUser,
|
||||
isAuthenticated,
|
||||
cellOwner,
|
||||
const result = await forumActions.moderateUser(
|
||||
{ cellId, userAddress, reason, currentUser, isAuthenticated, cellOwner },
|
||||
updateStateFromCache
|
||||
);
|
||||
|
||||
|
||||
534
src/lib/forum/ForumActions.ts
Normal file
534
src/lib/forum/ForumActions.ts
Normal file
@ -0,0 +1,534 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import {
|
||||
MessageType,
|
||||
UnsignedCellMessage,
|
||||
UnsignedCommentMessage,
|
||||
UnsignedPostMessage,
|
||||
UnsignedVoteMessage,
|
||||
UnsignedModerateMessage,
|
||||
CellMessage,
|
||||
CommentMessage,
|
||||
PostMessage,
|
||||
} from '@/types/waku';
|
||||
import { Cell, Comment, Post } from '@/types/forum';
|
||||
import { EVerificationStatus, User } from '@/types/identity';
|
||||
import { transformCell, transformComment, transformPost } from './transformers';
|
||||
import { MessageService, CryptoService } from '@/lib/services';
|
||||
|
||||
type ActionResult<T> = {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export class ForumActions {
|
||||
private cryptoService: CryptoService;
|
||||
private messageService: MessageService;
|
||||
|
||||
constructor(cryptoService?: CryptoService) {
|
||||
this.cryptoService = cryptoService || new CryptoService();
|
||||
this.messageService = new MessageService(this.cryptoService);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
POST / COMMENT / CELL CREATION
|
||||
-------------------------------------------------------------------*/
|
||||
|
||||
async createPost(
|
||||
params: PostCreationParams,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<Post>> {
|
||||
const { cellId, title, content, currentUser, isAuthenticated } = params;
|
||||
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to connect your wallet to post.',
|
||||
};
|
||||
}
|
||||
|
||||
// Check if user has basic verification or better, or owns ENS/Ordinal
|
||||
const hasENSOrOrdinal = !!(
|
||||
currentUser.ensDetails || currentUser.ordinalDetails
|
||||
);
|
||||
const isVerified =
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFIED_OWNER ||
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFIED_BASIC ||
|
||||
hasENSOrOrdinal;
|
||||
|
||||
if (
|
||||
!isVerified &&
|
||||
(currentUser.verificationStatus === EVerificationStatus.UNVERIFIED ||
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFYING)
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Verification required. Please complete wallet verification to post.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const postId = uuidv4();
|
||||
const postMessage: UnsignedPostMessage = {
|
||||
type: MessageType.POST,
|
||||
id: postId,
|
||||
cellId,
|
||||
title,
|
||||
content,
|
||||
timestamp: Date.now(),
|
||||
author: currentUser.address,
|
||||
};
|
||||
|
||||
const result = await this.messageService.sendMessage(postMessage);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.error || 'Failed to create post. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
const transformedPost = transformPost(result.message! as PostMessage);
|
||||
if (!transformedPost) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to transform post data.',
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: transformedPost,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error creating post:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to create post. Please try again.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async createComment(
|
||||
params: CommentCreationParams,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<Comment>> {
|
||||
const { postId, content, currentUser, isAuthenticated } = params;
|
||||
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to connect your wallet to comment.',
|
||||
};
|
||||
}
|
||||
|
||||
// Check if user has basic verification or better, or owns ENS/Ordinal
|
||||
const hasENSOrOrdinal = !!(
|
||||
currentUser.ensDetails || currentUser.ordinalDetails
|
||||
);
|
||||
const isVerified =
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFIED_OWNER ||
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFIED_BASIC ||
|
||||
hasENSOrOrdinal;
|
||||
|
||||
if (
|
||||
!isVerified &&
|
||||
(currentUser.verificationStatus === EVerificationStatus.UNVERIFIED ||
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFYING)
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Verification required. Please complete wallet verification to comment.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const commentId = uuidv4();
|
||||
const commentMessage: UnsignedCommentMessage = {
|
||||
type: MessageType.COMMENT,
|
||||
id: commentId,
|
||||
postId,
|
||||
content,
|
||||
timestamp: Date.now(),
|
||||
author: currentUser.address,
|
||||
};
|
||||
|
||||
const result = await this.messageService.sendMessage(commentMessage);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.error || 'Failed to add comment. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
const transformedComment = transformComment(
|
||||
result.message! as CommentMessage
|
||||
);
|
||||
if (!transformedComment) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to transform comment data.',
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: transformedComment,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error creating comment:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to add comment. Please try again.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async createCell(
|
||||
params: CellCreationParams,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<Cell>> {
|
||||
const { name, description, icon, currentUser, isAuthenticated } = params;
|
||||
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to verify Ordinal ownership to create a cell.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const cellId = uuidv4();
|
||||
const cellMessage: UnsignedCellMessage = {
|
||||
type: MessageType.CELL,
|
||||
id: cellId,
|
||||
name,
|
||||
description,
|
||||
...(icon && { icon }),
|
||||
timestamp: Date.now(),
|
||||
author: currentUser.address,
|
||||
};
|
||||
|
||||
const result = await this.messageService.sendMessage(cellMessage);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.error || 'Failed to create cell. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
const transformedCell = transformCell(result.message! as CellMessage);
|
||||
if (!transformedCell) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to transform cell data.',
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: transformedCell,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error creating cell:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to create cell. Please try again.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
VOTING
|
||||
-------------------------------------------------------------------*/
|
||||
|
||||
async vote(
|
||||
params: VoteParams,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<boolean>> {
|
||||
const { targetId, isUpvote, currentUser, isAuthenticated } = params;
|
||||
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to connect your wallet to vote.',
|
||||
};
|
||||
}
|
||||
|
||||
// Check if user has basic verification or better, or owns ENS/Ordinal
|
||||
const hasENSOrOrdinal = !!(
|
||||
currentUser.ensDetails || currentUser.ordinalDetails
|
||||
);
|
||||
const isVerified =
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFIED_OWNER ||
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFIED_BASIC ||
|
||||
hasENSOrOrdinal;
|
||||
|
||||
if (
|
||||
!isVerified &&
|
||||
(currentUser.verificationStatus === EVerificationStatus.UNVERIFIED ||
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFYING)
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Verification required. Please complete wallet verification to vote.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const voteId = uuidv4();
|
||||
const voteMessage: UnsignedVoteMessage = {
|
||||
type: MessageType.VOTE,
|
||||
id: voteId,
|
||||
targetId,
|
||||
value: isUpvote ? 1 : -1,
|
||||
timestamp: Date.now(),
|
||||
author: currentUser.address,
|
||||
};
|
||||
|
||||
const result = await this.messageService.sendMessage(voteMessage);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
result.error || 'Failed to register your vote. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
return {
|
||||
success: true,
|
||||
data: true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error voting:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to register your vote. Please try again.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
MODERATION
|
||||
-------------------------------------------------------------------*/
|
||||
|
||||
async moderatePost(
|
||||
params: PostModerationParams,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<boolean>> {
|
||||
const { cellId, postId, reason, currentUser, isAuthenticated, cellOwner } = params;
|
||||
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to verify Ordinal ownership to moderate posts.',
|
||||
};
|
||||
}
|
||||
if (currentUser.address !== cellOwner) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authorized. Only the cell admin can moderate posts.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const modMsg: UnsignedModerateMessage = {
|
||||
type: MessageType.MODERATE,
|
||||
id: uuidv4(),
|
||||
cellId,
|
||||
targetType: 'post',
|
||||
targetId: postId,
|
||||
reason,
|
||||
timestamp: Date.now(),
|
||||
author: currentUser.address,
|
||||
};
|
||||
|
||||
const result = await this.messageService.sendMessage(modMsg);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.error || 'Failed to moderate post. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
return {
|
||||
success: true,
|
||||
data: true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error moderating post:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to moderate post. Please try again.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async moderateComment(
|
||||
params: CommentModerationParams,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<boolean>> {
|
||||
const { cellId, commentId, reason, currentUser, isAuthenticated, cellOwner } = params;
|
||||
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to verify Ordinal ownership to moderate comments.',
|
||||
};
|
||||
}
|
||||
if (currentUser.address !== cellOwner) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authorized. Only the cell admin can moderate comments.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const modMsg: UnsignedModerateMessage = {
|
||||
type: MessageType.MODERATE,
|
||||
id: uuidv4(),
|
||||
cellId,
|
||||
targetType: 'comment',
|
||||
targetId: commentId,
|
||||
reason,
|
||||
timestamp: Date.now(),
|
||||
author: currentUser.address,
|
||||
};
|
||||
|
||||
const result = await this.messageService.sendMessage(modMsg);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
result.error || 'Failed to moderate comment. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
return {
|
||||
success: true,
|
||||
data: true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error moderating comment:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to moderate comment. Please try again.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async moderateUser(
|
||||
params: UserModerationParams,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<boolean>> {
|
||||
const { cellId, userAddress, reason, currentUser, isAuthenticated, cellOwner } = params;
|
||||
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to verify Ordinal ownership to moderate users.',
|
||||
};
|
||||
}
|
||||
if (currentUser.address !== cellOwner) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authorized. Only the cell admin can moderate users.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const modMsg: UnsignedModerateMessage = {
|
||||
type: MessageType.MODERATE,
|
||||
id: uuidv4(),
|
||||
cellId,
|
||||
targetType: 'user',
|
||||
targetId: userAddress,
|
||||
reason,
|
||||
author: currentUser.address,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
const result = await this.messageService.sendMessage(modMsg);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.error || 'Failed to moderate user. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
return {
|
||||
success: true,
|
||||
data: true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error moderating user:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to moderate user. Please try again.',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Base interface for all actions that require user authentication
|
||||
interface BaseActionParams {
|
||||
currentUser: User | null;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
// Parameter interfaces for all action methods
|
||||
interface PostCreationParams extends BaseActionParams {
|
||||
cellId: string;
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface CommentCreationParams extends BaseActionParams {
|
||||
postId: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface CellCreationParams extends BaseActionParams {
|
||||
name: string;
|
||||
description: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
interface VoteParams extends BaseActionParams {
|
||||
targetId: string;
|
||||
isUpvote: boolean;
|
||||
}
|
||||
|
||||
interface PostModerationParams extends BaseActionParams {
|
||||
cellId: string;
|
||||
postId: string;
|
||||
reason?: string;
|
||||
cellOwner: string;
|
||||
}
|
||||
|
||||
interface CommentModerationParams extends BaseActionParams {
|
||||
cellId: string;
|
||||
commentId: string;
|
||||
reason?: string;
|
||||
cellOwner: string;
|
||||
}
|
||||
|
||||
interface UserModerationParams extends BaseActionParams {
|
||||
cellId: string;
|
||||
userAddress: string;
|
||||
reason?: string;
|
||||
cellOwner: string;
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { RelevanceCalculator } from '../relevance';
|
||||
import { RelevanceCalculator } from '../RelevanceCalculator';
|
||||
import { Post, Comment, UserVerificationStatus } from '@/types/forum';
|
||||
import { User, EVerificationStatus, DisplayPreference } from '@/types/identity';
|
||||
import { VoteMessage, MessageType } from '@/types/waku';
|
||||
|
||||
@ -1,504 +0,0 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import {
|
||||
MessageType,
|
||||
UnsignedCellMessage,
|
||||
UnsignedCommentMessage,
|
||||
UnsignedPostMessage,
|
||||
UnsignedVoteMessage,
|
||||
UnsignedModerateMessage,
|
||||
CellMessage,
|
||||
CommentMessage,
|
||||
PostMessage,
|
||||
} from '@/types/waku';
|
||||
import { Cell, Comment, Post } from '@/types/forum';
|
||||
import { EVerificationStatus, User } from '@/types/identity';
|
||||
import { transformCell, transformComment, transformPost } from './transformers';
|
||||
import { MessageService, CryptoService } from '@/lib/services';
|
||||
|
||||
// Result types for action functions
|
||||
type ActionResult<T> = {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
POST / COMMENT / CELL CREATION
|
||||
-------------------------------------------------------------------*/
|
||||
|
||||
interface PostCreationParams {
|
||||
cellId: string;
|
||||
title: string;
|
||||
content: string;
|
||||
currentUser: User | null;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
export const createPost = async (
|
||||
{ cellId, title, content, currentUser, isAuthenticated }: PostCreationParams,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<Post>> => {
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to connect your wallet to post.',
|
||||
};
|
||||
}
|
||||
|
||||
// Check if user has basic verification or better, or owns ENS/Ordinal
|
||||
const hasENSOrOrdinal = !!(
|
||||
currentUser.ensDetails || currentUser.ordinalDetails
|
||||
);
|
||||
const isVerified =
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFIED_OWNER ||
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFIED_BASIC ||
|
||||
hasENSOrOrdinal;
|
||||
|
||||
if (
|
||||
!isVerified &&
|
||||
(currentUser.verificationStatus === EVerificationStatus.UNVERIFIED ||
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFYING)
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Verification required. Please complete wallet verification to post.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const postId = uuidv4();
|
||||
const postMessage: UnsignedPostMessage = {
|
||||
type: MessageType.POST,
|
||||
id: postId,
|
||||
cellId,
|
||||
title,
|
||||
content,
|
||||
timestamp: Date.now(),
|
||||
author: currentUser.address,
|
||||
};
|
||||
|
||||
const cryptoService = new CryptoService();
|
||||
const messageService = new MessageService(cryptoService);
|
||||
const result = await messageService.sendMessage(postMessage);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.error || 'Failed to create post. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
const transformedPost = transformPost(result.message! as PostMessage);
|
||||
if (!transformedPost) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to transform post data.',
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: transformedPost,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error creating post:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to create post. Please try again.',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const createComment = async (
|
||||
postId: string,
|
||||
content: string,
|
||||
currentUser: User | null,
|
||||
isAuthenticated: boolean,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<Comment>> => {
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to connect your wallet to comment.',
|
||||
};
|
||||
}
|
||||
|
||||
// Check if user has basic verification or better, or owns ENS/Ordinal
|
||||
const hasENSOrOrdinal = !!(
|
||||
currentUser.ensDetails || currentUser.ordinalDetails
|
||||
);
|
||||
const isVerified =
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFIED_OWNER ||
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFIED_BASIC ||
|
||||
hasENSOrOrdinal;
|
||||
|
||||
if (
|
||||
!isVerified &&
|
||||
(currentUser.verificationStatus === EVerificationStatus.UNVERIFIED ||
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFYING)
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Verification required. Please complete wallet verification to comment.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const commentId = uuidv4();
|
||||
const commentMessage: UnsignedCommentMessage = {
|
||||
type: MessageType.COMMENT,
|
||||
id: commentId,
|
||||
postId,
|
||||
content,
|
||||
timestamp: Date.now(),
|
||||
author: currentUser.address,
|
||||
};
|
||||
|
||||
const cryptoService = new CryptoService();
|
||||
const messageService = new MessageService(cryptoService);
|
||||
const result = await messageService.sendMessage(commentMessage);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.error || 'Failed to add comment. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
const transformedComment = transformComment(
|
||||
result.message! as CommentMessage
|
||||
);
|
||||
if (!transformedComment) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to transform comment data.',
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: transformedComment,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error creating comment:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to add comment. Please try again.',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const createCell = async (
|
||||
name: string,
|
||||
description: string,
|
||||
icon: string | undefined,
|
||||
currentUser: User | null,
|
||||
isAuthenticated: boolean,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<Cell>> => {
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to verify Ordinal ownership to create a cell.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const cellId = uuidv4();
|
||||
const cellMessage: UnsignedCellMessage = {
|
||||
type: MessageType.CELL,
|
||||
id: cellId,
|
||||
name,
|
||||
description,
|
||||
...(icon && { icon }),
|
||||
timestamp: Date.now(),
|
||||
author: currentUser.address,
|
||||
};
|
||||
|
||||
const cryptoService = new CryptoService();
|
||||
const messageService = new MessageService(cryptoService);
|
||||
const result = await messageService.sendMessage(cellMessage);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.error || 'Failed to create cell. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
const transformedCell = transformCell(result.message! as CellMessage);
|
||||
if (!transformedCell) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to transform cell data.',
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: transformedCell,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error creating cell:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to create cell. Please try again.',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
VOTING
|
||||
-------------------------------------------------------------------*/
|
||||
|
||||
export const vote = async (
|
||||
targetId: string,
|
||||
isUpvote: boolean,
|
||||
currentUser: User | null,
|
||||
isAuthenticated: boolean,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<boolean>> => {
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to connect your wallet to vote.',
|
||||
};
|
||||
}
|
||||
|
||||
// Check if user has basic verification or better, or owns ENS/Ordinal
|
||||
const hasENSOrOrdinal = !!(
|
||||
currentUser.ensDetails || currentUser.ordinalDetails
|
||||
);
|
||||
const isVerified =
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFIED_OWNER ||
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFIED_BASIC ||
|
||||
hasENSOrOrdinal;
|
||||
|
||||
if (
|
||||
!isVerified &&
|
||||
(currentUser.verificationStatus === EVerificationStatus.UNVERIFIED ||
|
||||
currentUser.verificationStatus === EVerificationStatus.VERIFYING)
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Verification required. Please complete wallet verification to vote.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const voteId = uuidv4();
|
||||
const voteMessage: UnsignedVoteMessage = {
|
||||
type: MessageType.VOTE,
|
||||
id: voteId,
|
||||
targetId,
|
||||
value: isUpvote ? 1 : -1,
|
||||
timestamp: Date.now(),
|
||||
author: currentUser.address,
|
||||
};
|
||||
|
||||
const cryptoService = new CryptoService();
|
||||
const messageService = new MessageService(cryptoService);
|
||||
const result = await messageService.sendMessage(voteMessage);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
result.error || 'Failed to register your vote. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
return {
|
||||
success: true,
|
||||
data: true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error voting:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to register your vote. Please try again.',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
MODERATION
|
||||
-------------------------------------------------------------------*/
|
||||
|
||||
export const moderatePost = async (
|
||||
cellId: string,
|
||||
postId: string,
|
||||
reason: string | undefined,
|
||||
currentUser: User | null,
|
||||
isAuthenticated: boolean,
|
||||
cellOwner: string,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<boolean>> => {
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to verify Ordinal ownership to moderate posts.',
|
||||
};
|
||||
}
|
||||
if (currentUser.address !== cellOwner) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authorized. Only the cell admin can moderate posts.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const modMsg: UnsignedModerateMessage = {
|
||||
type: MessageType.MODERATE,
|
||||
id: uuidv4(),
|
||||
cellId,
|
||||
targetType: 'post',
|
||||
targetId: postId,
|
||||
reason,
|
||||
timestamp: Date.now(),
|
||||
author: currentUser.address,
|
||||
};
|
||||
const cryptoService = new CryptoService();
|
||||
const messageService = new MessageService(cryptoService);
|
||||
const result = await messageService.sendMessage(modMsg);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.error || 'Failed to moderate post. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
return {
|
||||
success: true,
|
||||
data: true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error moderating post:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to moderate post. Please try again.',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const moderateComment = async (
|
||||
cellId: string,
|
||||
commentId: string,
|
||||
reason: string | undefined,
|
||||
currentUser: User | null,
|
||||
isAuthenticated: boolean,
|
||||
cellOwner: string,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<boolean>> => {
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to verify Ordinal ownership to moderate comments.',
|
||||
};
|
||||
}
|
||||
if (currentUser.address !== cellOwner) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authorized. Only the cell admin can moderate comments.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const modMsg: UnsignedModerateMessage = {
|
||||
type: MessageType.MODERATE,
|
||||
id: uuidv4(),
|
||||
cellId,
|
||||
targetType: 'comment',
|
||||
targetId: commentId,
|
||||
reason,
|
||||
timestamp: Date.now(),
|
||||
author: currentUser.address,
|
||||
};
|
||||
const cryptoService = new CryptoService();
|
||||
const messageService = new MessageService(cryptoService);
|
||||
const result = await messageService.sendMessage(modMsg);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.error || 'Failed to moderate comment. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
return {
|
||||
success: true,
|
||||
data: true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error moderating comment:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to moderate comment. Please try again.',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const moderateUser = async (
|
||||
cellId: string,
|
||||
userAddress: string,
|
||||
reason: string | undefined,
|
||||
currentUser: User | null,
|
||||
isAuthenticated: boolean,
|
||||
cellOwner: string,
|
||||
updateStateFromCache: () => void
|
||||
): Promise<ActionResult<boolean>> => {
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Authentication required. You need to verify Ordinal ownership to moderate users.',
|
||||
};
|
||||
}
|
||||
if (currentUser.address !== cellOwner) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authorized. Only the cell admin can moderate users.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const modMsg: UnsignedModerateMessage = {
|
||||
type: MessageType.MODERATE,
|
||||
id: uuidv4(),
|
||||
cellId,
|
||||
targetType: 'user',
|
||||
targetId: userAddress,
|
||||
reason,
|
||||
author: currentUser.address,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
const cryptoService = new CryptoService();
|
||||
const messageService = new MessageService(cryptoService);
|
||||
const result = await messageService.sendMessage(modMsg);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.error || 'Failed to moderate user. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
return {
|
||||
success: true,
|
||||
data: true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error moderating user:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to moderate user. Please try again.',
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -6,7 +6,7 @@ import {
|
||||
VoteMessage,
|
||||
} from '@/types/waku';
|
||||
import messageManager from '@/lib/waku';
|
||||
import { RelevanceCalculator } from './relevance';
|
||||
import { RelevanceCalculator } from './RelevanceCalculator';
|
||||
import { UserVerificationStatus } from '@/types/forum';
|
||||
import { MessageValidator } from '@/lib/utils/MessageValidator';
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ import PostCard from '@/components/PostCard';
|
||||
import FeedSidebar from '@/components/FeedSidebar';
|
||||
import { useForum } from '@/contexts/useForum';
|
||||
import { useAuth } from '@/contexts/useAuth';
|
||||
import { sortPosts, SortOption } from '@/lib/forum/sorting';
|
||||
import { sortPosts, SortOption } from '@/lib/utils/sorting';
|
||||
|
||||
const FeedPage: React.FC = () => {
|
||||
const { posts, comments, isInitialLoading, isRefreshing, refreshData } =
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user