"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;