Files
asle/backend/src/services/governance-discussion.ts
defiQUG 507d9a35b1 Add initial project structure and documentation files
- Created .gitignore to exclude sensitive files and directories.
- Added API documentation in API_DOCUMENTATION.md.
- Included deployment instructions in DEPLOYMENT.md.
- Established project structure documentation in PROJECT_STRUCTURE.md.
- Updated README.md with project status and team information.
- Added recommendations and status tracking documents.
- Introduced testing guidelines in TESTING.md.
- Set up CI workflow in .github/workflows/ci.yml.
- Created Dockerfile for backend and frontend setups.
- Added various service and utility files for backend functionality.
- Implemented frontend components and pages for user interface.
- Included mobile app structure and services.
- Established scripts for deployment across multiple chains.
2025-12-03 21:22:31 -08:00

166 lines
3.7 KiB
TypeScript

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export interface Comment {
id: string;
proposalId: bigint;
author: string;
content: string;
parentId?: string;
upvotes: number;
downvotes: number;
createdAt: Date;
updatedAt: Date;
}
export interface DiscussionThread {
proposalId: bigint;
comments: Comment[];
totalComments: number;
}
export class GovernanceDiscussionService {
/**
* Add comment to proposal
*/
async addComment(
proposalId: bigint,
author: string,
content: string,
parentId?: string
): Promise<Comment> {
const comment = await prisma.comment.create({
data: {
proposalId,
author,
content,
parentId,
upvotes: 0,
downvotes: 0,
},
});
return {
id: comment.id,
proposalId: comment.proposalId,
author: comment.author,
content: comment.content,
parentId: comment.parentId || undefined,
upvotes: comment.upvotes,
downvotes: comment.downvotes,
createdAt: comment.createdAt,
updatedAt: comment.updatedAt,
};
}
/**
* Get discussion thread for proposal
*/
async getDiscussion(proposalId: bigint): Promise<DiscussionThread> {
const comments = await prisma.comment.findMany({
where: { proposalId },
orderBy: { createdAt: 'asc' },
});
return {
proposalId,
comments: comments.map((c) => ({
id: c.id,
proposalId: c.proposalId,
author: c.author,
content: c.content,
parentId: c.parentId || undefined,
upvotes: c.upvotes,
downvotes: c.downvotes,
createdAt: c.createdAt,
updatedAt: c.updatedAt,
})),
totalComments: comments.length,
};
}
/**
* Vote on comment
*/
async voteComment(commentId: string, voter: string, upvote: boolean): Promise<void> {
const existingVote = await prisma.commentVote.findUnique({
where: {
commentId_voter: {
commentId,
voter,
},
},
});
if (existingVote) {
// Update existing vote
if (existingVote.upvote !== upvote) {
await prisma.commentVote.update({
where: { id: existingVote.id },
data: { upvote },
});
// Update comment vote counts
if (upvote) {
await prisma.comment.update({
where: { id: commentId },
data: {
upvotes: { increment: 1 },
downvotes: { decrement: 1 },
},
});
} else {
await prisma.comment.update({
where: { id: commentId },
data: {
upvotes: { decrement: 1 },
downvotes: { increment: 1 },
},
});
}
}
} else {
// Create new vote
await prisma.commentVote.create({
data: {
commentId,
voter,
upvote,
},
});
// Update comment vote counts
await prisma.comment.update({
where: { id: commentId },
data: {
upvotes: upvote ? { increment: 1 } : undefined,
downvotes: upvote ? undefined : { increment: 1 },
},
});
}
}
/**
* Delete comment
*/
async deleteComment(commentId: string, author: string): Promise<void> {
const comment = await prisma.comment.findUnique({
where: { id: commentId },
});
if (!comment) {
throw new Error('Comment not found');
}
if (comment.author.toLowerCase() !== author.toLowerCase()) {
throw new Error('Not authorized to delete this comment');
}
await prisma.comment.delete({
where: { id: commentId },
});
}
}