Files
promote/backend/dist/routes/projectComments.js
2025-03-07 18:04:27 +08:00

396 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const hono_1 = require("hono");
const auth_1 = require("../middlewares/auth");
const supabase_1 = __importDefault(require("../utils/supabase"));
const clickhouse_1 = __importDefault(require("../utils/clickhouse"));
const projectCommentsRouter = new hono_1.Hono();
// Apply auth middleware to all routes
projectCommentsRouter.use('*', auth_1.authMiddleware);
// 获取项目的评论列表
projectCommentsRouter.get('/projects/:id/comments', async (c) => {
try {
const projectId = c.req.param('id');
const { limit = '20', offset = '0', parent_id = null } = c.req.query();
// 检查项目是否存在
const { data: project, error: projectError } = await supabase_1.default
.from('projects')
.select('id, name')
.eq('id', projectId)
.single();
if (projectError) {
console.error('Error fetching project:', projectError);
return c.json({ error: 'Project not found' }, 404);
}
// 构建评论查询
let commentsQuery = supabase_1.default
.from('project_comments')
.select(`
comment_id,
project_id,
user_id,
content,
sentiment_score,
status,
is_pinned,
parent_id,
created_at,
updated_at,
user:user_id(id, email)
`, { count: 'exact' });
// 过滤条件
commentsQuery = commentsQuery.eq('project_id', projectId);
// 如果指定了父评论ID则获取子评论
if (parent_id) {
commentsQuery = commentsQuery.eq('parent_id', parent_id);
}
else {
// 否则获取顶级评论(没有父评论的评论)
commentsQuery = commentsQuery.is('parent_id', null);
}
// 排序和分页
const isPinned = parent_id ? false : true; // 只有顶级评论才考虑置顶
if (isPinned) {
commentsQuery = commentsQuery.order('is_pinned', { ascending: false });
}
commentsQuery = commentsQuery.order('created_at', { ascending: false });
commentsQuery = commentsQuery.range(parseInt(offset), parseInt(offset) + parseInt(limit) - 1);
// 执行查询
const { data: comments, error: commentsError, count } = await commentsQuery;
if (commentsError) {
console.error('Error fetching project comments:', commentsError);
return c.json({ error: 'Failed to fetch project comments' }, 500);
}
// 获取每个顶级评论的回复数量
if (comments && !parent_id) {
const commentIds = comments.map(comment => comment.comment_id);
if (commentIds.length > 0) {
// 手动构建SQL查询来计算每个父评论的回复数量
const { data: replyCounts, error: replyCountError } = await supabase_1.default
.rpc('get_reply_counts_for_comments', { parent_ids: commentIds });
if (!replyCountError && replyCounts) {
// 将回复数量添加到评论中
for (const comment of comments) {
const replyCountItem = replyCounts.find((r) => r.parent_id === comment.comment_id);
comment.reply_count = replyCountItem ? replyCountItem.count : 0;
}
}
}
}
return c.json({
project,
comments: comments || [],
total: count || 0,
limit: parseInt(limit),
offset: parseInt(offset)
});
}
catch (error) {
console.error('Error fetching project comments:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 添加评论到项目
projectCommentsRouter.post('/projects/:id/comments', async (c) => {
try {
const projectId = c.req.param('id');
const user = c.get('user');
const { content, sentiment_score = 0, parent_id = null } = await c.req.json();
if (!content) {
return c.json({ error: 'Comment content is required' }, 400);
}
// 检查项目是否存在
const { data: project, error: projectError } = await supabase_1.default
.from('projects')
.select('id')
.eq('id', projectId)
.single();
if (projectError) {
console.error('Error fetching project:', projectError);
return c.json({ error: 'Project not found' }, 404);
}
// 如果指定了父评论ID检查父评论是否存在
if (parent_id) {
const { data: parentComment, error: parentError } = await supabase_1.default
.from('project_comments')
.select('comment_id')
.eq('comment_id', parent_id)
.eq('project_id', projectId)
.single();
if (parentError || !parentComment) {
return c.json({ error: 'Parent comment not found' }, 404);
}
}
// 创建评论
const { data: comment, error: commentError } = await supabase_1.default
.from('project_comments')
.insert({
project_id: projectId,
user_id: user.id,
content,
sentiment_score,
parent_id
})
.select()
.single();
if (commentError) {
console.error('Error creating project comment:', commentError);
return c.json({ error: 'Failed to create comment' }, 500);
}
// 记录评论事件到ClickHouse
try {
await clickhouse_1.default.query({
query: `
INSERT INTO events (
project_id,
event_type,
metric_value,
event_metadata
) VALUES (?, 'project_comment', ?, ?)
`,
values: [
projectId,
1,
JSON.stringify({
comment_id: comment.comment_id,
user_id: user.id,
parent_id: parent_id || null,
content: content.substring(0, 100), // 只存储部分内容以减小数据量
sentiment_score: sentiment_score
})
]
});
}
catch (chError) {
console.error('Error recording project comment event:', chError);
// 继续执行,不中断主流程
}
return c.json({
message: 'Comment added successfully',
comment
}, 201);
}
catch (error) {
console.error('Error adding project comment:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 更新项目评论
projectCommentsRouter.put('/comments/:id', async (c) => {
try {
const commentId = c.req.param('id');
const user = c.get('user');
const { content, sentiment_score, is_pinned } = await c.req.json();
// 检查评论是否存在且属于当前用户或用户是项目拥有者
const { data: comment, error: fetchError } = await supabase_1.default
.from('project_comments')
.select(`
comment_id,
project_id,
user_id,
projects!inner(created_by)
`)
.eq('comment_id', commentId)
.single();
if (fetchError || !comment) {
return c.json({ error: 'Comment not found' }, 404);
}
// 确保我们能够安全地访问projects中的created_by字段
const projectOwner = comment.projects &&
Array.isArray(comment.projects) &&
comment.projects.length > 0 ?
comment.projects[0].created_by : null;
// 检查用户是否有权限更新评论
const isCommentOwner = comment.user_id === user.id;
const isProjectOwner = projectOwner === user.id;
if (!isCommentOwner && !isProjectOwner) {
return c.json({
error: 'You do not have permission to update this comment'
}, 403);
}
// 准备更新数据
const updateData = {};
// 评论创建者可以更新内容和情感分数
if (isCommentOwner) {
if (content !== undefined) {
updateData.content = content;
}
if (sentiment_score !== undefined) {
updateData.sentiment_score = sentiment_score;
}
}
// 项目所有者可以更新状态和置顶
if (isProjectOwner) {
if (is_pinned !== undefined) {
updateData.is_pinned = is_pinned;
}
}
// 更新时间
updateData.updated_at = new Date().toISOString();
// 如果没有内容要更新,返回错误
if (Object.keys(updateData).length === 1) { // 只有updated_at
return c.json({ error: 'No valid fields to update' }, 400);
}
// 更新评论
const { data: updatedComment, error } = await supabase_1.default
.from('project_comments')
.update(updateData)
.eq('comment_id', commentId)
.select()
.single();
if (error) {
console.error('Error updating project comment:', error);
return c.json({ error: 'Failed to update comment' }, 500);
}
return c.json({
message: 'Comment updated successfully',
comment: updatedComment
});
}
catch (error) {
console.error('Error updating project comment:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 删除项目评论
projectCommentsRouter.delete('/comments/:id', async (c) => {
try {
const commentId = c.req.param('id');
const user = c.get('user');
// 检查评论是否存在且属于当前用户或用户是项目拥有者
const { data: comment, error: fetchError } = await supabase_1.default
.from('project_comments')
.select(`
comment_id,
project_id,
user_id,
projects!inner(created_by)
`)
.eq('comment_id', commentId)
.single();
if (fetchError || !comment) {
return c.json({ error: 'Comment not found' }, 404);
}
// 确保我们能够安全地访问projects中的created_by字段
const projectOwner = comment.projects &&
Array.isArray(comment.projects) &&
comment.projects.length > 0 ?
comment.projects[0].created_by : null;
// 检查用户是否有权限删除评论
const isCommentOwner = comment.user_id === user.id;
const isProjectOwner = projectOwner === user.id;
if (!isCommentOwner && !isProjectOwner) {
return c.json({
error: 'You do not have permission to delete this comment'
}, 403);
}
// 删除评论
const { error } = await supabase_1.default
.from('project_comments')
.delete()
.eq('comment_id', commentId);
if (error) {
console.error('Error deleting project comment:', error);
return c.json({ error: 'Failed to delete comment' }, 500);
}
return c.json({
message: 'Comment deleted successfully'
});
}
catch (error) {
console.error('Error deleting project comment:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 获取项目评论的统计信息
projectCommentsRouter.get('/projects/:id/comments/stats', async (c) => {
try {
const projectId = c.req.param('id');
// 检查项目是否存在
const { data: project, error: projectError } = await supabase_1.default
.from('projects')
.select('id, name')
.eq('id', projectId)
.single();
if (projectError) {
console.error('Error fetching project:', projectError);
return c.json({ error: 'Project not found' }, 404);
}
// 从Supabase获取评论总数
const { count } = await supabase_1.default
.from('project_comments')
.select('*', { count: 'exact', head: true })
.eq('project_id', projectId);
// 从Supabase获取情感分析统计
const { data: sentimentStats } = await supabase_1.default
.from('project_comments')
.select('sentiment_score')
.eq('project_id', projectId);
let averageSentiment = 0;
let positiveCount = 0;
let neutralCount = 0;
let negativeCount = 0;
if (sentimentStats && sentimentStats.length > 0) {
// 计算平均情感分数
const totalSentiment = sentimentStats.reduce((acc, curr) => acc + (curr.sentiment_score || 0), 0);
averageSentiment = totalSentiment / sentimentStats.length;
// 分类情感分数
sentimentStats.forEach(stat => {
const score = stat.sentiment_score || 0;
if (score > 0.3) {
positiveCount++;
}
else if (score < -0.3) {
negativeCount++;
}
else {
neutralCount++;
}
});
}
let timeTrend = [];
try {
const result = await clickhouse_1.default.query({
query: `
SELECT
toDate(timestamp) as date,
count() as comment_count
FROM events
WHERE
project_id = ? AND
event_type = 'project_comment' AND
timestamp >= subtractDays(now(), 30)
GROUP BY date
ORDER BY date ASC
`,
values: [projectId]
});
timeTrend = 'rows' in result ? result.rows : [];
}
catch (chError) {
console.error('Error fetching comment time trend:', chError);
// 继续执行,返回空趋势数据
}
return c.json({
project_id: projectId,
project_name: project.name,
total_comments: count || 0,
sentiment: {
average: averageSentiment,
positive: positiveCount,
neutral: neutralCount,
negative: negativeCount
},
time_trend: timeTrend
});
}
catch (error) {
console.error('Error fetching project comment stats:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
exports.default = projectCommentsRouter;