"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 clickhouse_1 = __importDefault(require("../utils/clickhouse")); const queue_1 = require("../utils/queue"); const redis_1 = require("../utils/redis"); const supabase_1 = __importDefault(require("../utils/supabase")); const analyticsRouter = new hono_1.Hono(); // Apply auth middleware to all routes analyticsRouter.use('*', auth_1.authMiddleware); // Track a view event analyticsRouter.post('/view', async (c) => { try { const { content_id } = await c.req.json(); const user = c.get('user'); if (!content_id) { return c.json({ error: 'Content ID is required' }, 400); } // Get IP and user agent const ip = c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || '0.0.0.0'; const userAgent = c.req.header('user-agent') || 'unknown'; // Insert view event into ClickHouse await clickhouse_1.default.query({ query: ` INSERT INTO promote.view_events (user_id, content_id, ip, user_agent) VALUES (?, ?, ?, ?) `, values: [ user.id, content_id, ip, userAgent ] }); // Queue analytics processing job await (0, queue_1.addAnalyticsJob)('process_views', { user_id: user.id, content_id, timestamp: new Date().toISOString() }); // Increment view count in Redis cache const redis = await (0, redis_1.getRedisClient)(); await redis.incr(`views:${content_id}`); return c.json({ message: 'View tracked successfully' }); } catch (error) { console.error('View tracking error:', error); return c.json({ error: 'Internal server error' }, 500); } }); // Track a like event analyticsRouter.post('/like', async (c) => { try { const { content_id, action } = await c.req.json(); const user = c.get('user'); if (!content_id || !action) { return c.json({ error: 'Content ID and action are required' }, 400); } if (action !== 'like' && action !== 'unlike') { return c.json({ error: 'Action must be either "like" or "unlike"' }, 400); } // Insert like event into ClickHouse await clickhouse_1.default.query({ query: ` INSERT INTO promote.like_events (user_id, content_id, action) VALUES (?, ?, ?) `, values: [ user.id, content_id, action === 'like' ? 1 : 2 ] }); // Queue analytics processing job await (0, queue_1.addAnalyticsJob)('process_likes', { user_id: user.id, content_id, action, timestamp: new Date().toISOString() }); // Update like count in Redis cache const redis = await (0, redis_1.getRedisClient)(); const likeKey = `likes:${content_id}`; if (action === 'like') { await redis.incr(likeKey); } else { await redis.decr(likeKey); } return c.json({ message: `${action} tracked successfully` }); } catch (error) { console.error('Like tracking error:', error); return c.json({ error: 'Internal server error' }, 500); } }); // Track a follow event analyticsRouter.post('/follow', async (c) => { try { const { followed_id, action } = await c.req.json(); const user = c.get('user'); if (!followed_id || !action) { return c.json({ error: 'Followed ID and action are required' }, 400); } if (action !== 'follow' && action !== 'unfollow') { return c.json({ error: 'Action must be either "follow" or "unfollow"' }, 400); } // Insert follower event into ClickHouse await clickhouse_1.default.query({ query: ` INSERT INTO promote.follower_events (follower_id, followed_id, action) VALUES (?, ?, ?) `, values: [ user.id, followed_id, action === 'follow' ? 1 : 2 ] }); // Queue analytics processing job await (0, queue_1.addAnalyticsJob)('process_followers', { follower_id: user.id, followed_id, action, timestamp: new Date().toISOString() }); // Update follower count in Redis cache const redis = await (0, redis_1.getRedisClient)(); const followerKey = `followers:${followed_id}`; if (action === 'follow') { await redis.incr(followerKey); } else { await redis.decr(followerKey); } return c.json({ message: `${action} tracked successfully` }); } catch (error) { console.error('Follow tracking error:', error); return c.json({ error: 'Internal server error' }, 500); } }); // Get analytics for a content analyticsRouter.get('/content/:id', async (c) => { try { const contentId = c.req.param('id'); // Get counts from Redis cache const redis = await (0, redis_1.getRedisClient)(); const [views, likes] = await Promise.all([ redis.get(`views:${contentId}`), redis.get(`likes:${contentId}`) ]); return c.json({ content_id: contentId, views: parseInt(views || '0'), likes: parseInt(likes || '0') }); } catch (error) { console.error('Content analytics error:', error); return c.json({ error: 'Internal server error' }, 500); } }); // Get analytics for a user analyticsRouter.get('/user/:id', async (c) => { try { const userId = c.req.param('id'); // Get follower count from Redis cache const redis = await (0, redis_1.getRedisClient)(); const followers = await redis.get(`followers:${userId}`); // Get content view and like counts from ClickHouse const viewsResult = await clickhouse_1.default.query({ query: ` SELECT content_id, COUNT(*) as view_count FROM promote.view_events WHERE user_id = ? GROUP BY content_id `, values: [userId] }); const likesResult = await clickhouse_1.default.query({ query: ` SELECT content_id, SUM(CASE WHEN action = 1 THEN 1 ELSE -1 END) as like_count FROM promote.like_events WHERE user_id = ? GROUP BY content_id `, values: [userId] }); // Extract data from results const viewsData = 'rows' in viewsResult ? viewsResult.rows : []; const likesData = 'rows' in likesResult ? likesResult.rows : []; return c.json({ user_id: userId, followers: parseInt(followers || '0'), content_analytics: { views: viewsData, likes: likesData } }); } catch (error) { console.error('User analytics error:', error); return c.json({ error: 'Internal server error' }, 500); } }); // 社群分析相关路由 // 获取项目的顶级影响者 analyticsRouter.get('/project/:id/top-influencers', async (c) => { try { const projectId = c.req.param('id'); // 从ClickHouse查询项目的顶级影响者 const result = await clickhouse_1.default.query({ query: ` SELECT influencer_id, SUM(metric_value) AS total_views FROM events WHERE project_id = ? AND event_type = 'post_view_change' GROUP BY influencer_id ORDER BY total_views DESC LIMIT 10 `, values: [projectId] }); // 提取数据 const influencerData = 'rows' in result ? result.rows : []; // 如果有数据,从Supabase获取影响者详细信息 if (influencerData.length > 0) { const influencerIds = influencerData.map((item) => item.influencer_id); const { data: influencerDetails, error } = await supabase_1.default .from('influencers') .select('influencer_id, name, platform, followers_count, video_count') .in('influencer_id', influencerIds); if (error) { console.error('Error fetching influencer details:', error); return c.json({ error: 'Error fetching influencer details' }, 500); } // 合并数据 const enrichedData = influencerData.map((item) => { const details = influencerDetails?.find((detail) => detail.influencer_id === item.influencer_id) || {}; return { ...item, ...details }; }); return c.json(enrichedData); } return c.json(influencerData); } catch (error) { console.error('Error fetching top influencers:', error); return c.json({ error: 'Internal server error' }, 500); } }); // 获取影响者的粉丝变化趋势(过去6个月) analyticsRouter.get('/influencer/:id/follower-trend', async (c) => { try { const influencerId = c.req.param('id'); // 从ClickHouse查询影响者的粉丝变化趋势 const result = await clickhouse_1.default.query({ query: ` SELECT toStartOfMonth(timestamp) AS month, SUM(metric_value) AS follower_change FROM events WHERE influencer_id = ? AND event_type = 'follower_change' AND timestamp >= subtractMonths(now(), 6) GROUP BY month ORDER BY month ASC `, values: [influencerId] }); // 提取数据 const trendData = 'rows' in result ? result.rows : []; return c.json({ influencer_id: influencerId, follower_trend: trendData }); } catch (error) { console.error('Error fetching follower trend:', error); return c.json({ error: 'Internal server error' }, 500); } }); // 获取帖子的点赞变化(过去30天) analyticsRouter.get('/post/:id/like-trend', async (c) => { try { const postId = c.req.param('id'); // 从ClickHouse查询帖子的点赞变化 const result = await clickhouse_1.default.query({ query: ` SELECT toDate(timestamp) AS day, SUM(metric_value) AS like_change FROM events WHERE post_id = ? AND event_type = 'post_like_change' AND timestamp >= subtractDays(now(), 30) GROUP BY day ORDER BY day ASC `, values: [postId] }); // 提取数据 const trendData = 'rows' in result ? result.rows : []; return c.json({ post_id: postId, like_trend: trendData }); } catch (error) { console.error('Error fetching like trend:', error); return c.json({ error: 'Internal server error' }, 500); } }); // 获取影响者详细信息 analyticsRouter.get('/influencer/:id/details', async (c) => { try { const influencerId = c.req.param('id'); // 从Supabase获取影响者详细信息 const { data, error } = await supabase_1.default .from('influencers') .select('influencer_id, name, platform, profile_url, external_id, followers_count, video_count, platform_count, created_at') .eq('influencer_id', influencerId) .single(); if (error) { console.error('Error fetching influencer details:', error); return c.json({ error: 'Error fetching influencer details' }, 500); } if (!data) { return c.json({ error: 'Influencer not found' }, 404); } return c.json(data); } catch (error) { console.error('Error fetching influencer details:', error); return c.json({ error: 'Internal server error' }, 500); } }); // 获取影响者的帖子列表 analyticsRouter.get('/influencer/:id/posts', async (c) => { try { const influencerId = c.req.param('id'); // 从Supabase获取影响者的帖子列表 const { data, error } = await supabase_1.default .from('posts') .select('post_id, influencer_id, platform, post_url, title, description, published_at, created_at') .eq('influencer_id', influencerId) .order('published_at', { ascending: false }); if (error) { console.error('Error fetching influencer posts:', error); return c.json({ error: 'Error fetching influencer posts' }, 500); } return c.json(data || []); } catch (error) { console.error('Error fetching influencer posts:', error); return c.json({ error: 'Internal server error' }, 500); } }); // 获取帖子的评论列表 analyticsRouter.get('/post/:id/comments', async (c) => { try { const postId = c.req.param('id'); // 从Supabase获取帖子的评论列表 const { data, error } = await supabase_1.default .from('comments') .select('comment_id, post_id, user_id, content, sentiment_score, created_at') .eq('post_id', postId) .order('created_at', { ascending: false }); if (error) { console.error('Error fetching post comments:', error); return c.json({ error: 'Error fetching post comments' }, 500); } return c.json(data || []); } catch (error) { console.error('Error fetching post comments:', error); return c.json({ error: 'Internal server error' }, 500); } }); // 获取项目的平台分布 analyticsRouter.get('/project/:id/platform-distribution', async (c) => { try { const projectId = c.req.param('id'); // 从ClickHouse查询项目的平台分布 const result = await clickhouse_1.default.query({ query: ` SELECT platform, COUNT(DISTINCT influencer_id) AS influencer_count FROM events WHERE project_id = ? GROUP BY platform ORDER BY influencer_count DESC `, values: [projectId] }); // 提取数据 const distributionData = 'rows' in result ? result.rows : []; return c.json({ project_id: projectId, platform_distribution: distributionData }); } catch (error) { console.error('Error fetching platform distribution:', error); return c.json({ error: 'Internal server error' }, 500); } }); // 获取项目的互动类型分布 analyticsRouter.get('/project/:id/interaction-types', async (c) => { try { const projectId = c.req.param('id'); // 从ClickHouse查询项目的互动类型分布 const result = await clickhouse_1.default.query({ query: ` SELECT event_type, COUNT(*) AS event_count, SUM(metric_value) AS total_value FROM events WHERE project_id = ? AND event_type IN ('click', 'comment', 'share') GROUP BY event_type ORDER BY event_count DESC `, values: [projectId] }); // 提取数据 const interactionData = 'rows' in result ? result.rows : []; return c.json({ project_id: projectId, interaction_types: interactionData }); } catch (error) { console.error('Error fetching interaction types:', error); return c.json({ error: 'Internal server error' }, 500); } }); exports.default = analyticsRouter;