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

650 lines
22 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 clickhouse_1 = __importDefault(require("../utils/clickhouse"));
const supabase_1 = __importDefault(require("../utils/supabase"));
const communityRouter = new hono_1.Hono();
// Apply auth middleware to all routes
communityRouter.use('*', auth_1.authMiddleware);
// 创建新项目
communityRouter.post('/projects', async (c) => {
try {
const { name, description, start_date, end_date } = await c.req.json();
const user = c.get('user');
if (!name) {
return c.json({ error: 'Project name is required' }, 400);
}
// 在Supabase中创建项目
const { data, error } = await supabase_1.default
.from('projects')
.insert({
name,
description,
start_date,
end_date,
created_by: user.id
})
.select()
.single();
if (error) {
console.error('Error creating project:', error);
return c.json({ error: 'Failed to create project' }, 500);
}
return c.json({
message: 'Project created successfully',
project: data
}, 201);
}
catch (error) {
console.error('Error creating project:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 获取项目列表
communityRouter.get('/projects', async (c) => {
try {
const user = c.get('user');
// 从Supabase获取项目列表
const { data, error } = await supabase_1.default
.from('projects')
.select('*')
.eq('created_by', user.id)
.order('created_at', { ascending: false });
if (error) {
console.error('Error fetching projects:', error);
return c.json({ error: 'Failed to fetch projects' }, 500);
}
return c.json(data || []);
}
catch (error) {
console.error('Error fetching projects:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 获取项目详情
communityRouter.get('/projects/:id', async (c) => {
try {
const projectId = c.req.param('id');
// 从Supabase获取项目详情
const { data, error } = await supabase_1.default
.from('projects')
.select('*')
.eq('id', projectId)
.single();
if (error) {
console.error('Error fetching project:', error);
return c.json({ error: 'Failed to fetch project' }, 500);
}
if (!data) {
return c.json({ error: 'Project not found' }, 404);
}
return c.json(data);
}
catch (error) {
console.error('Error fetching project:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 更新项目
communityRouter.put('/projects/:id', async (c) => {
try {
const projectId = c.req.param('id');
const { name, description, start_date, end_date, status } = await c.req.json();
const user = c.get('user');
// 检查项目是否存在并属于当前用户
const { data: existingProject, error: fetchError } = await supabase_1.default
.from('projects')
.select('*')
.eq('id', projectId)
.eq('created_by', user.id)
.single();
if (fetchError || !existingProject) {
return c.json({ error: 'Project not found or you do not have permission to update it' }, 404);
}
// 更新项目
const { data, error } = await supabase_1.default
.from('projects')
.update({
name,
description,
start_date,
end_date,
status,
updated_at: new Date().toISOString()
})
.eq('id', projectId)
.select()
.single();
if (error) {
console.error('Error updating project:', error);
return c.json({ error: 'Failed to update project' }, 500);
}
return c.json({
message: 'Project updated successfully',
project: data
});
}
catch (error) {
console.error('Error updating project:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 删除项目
communityRouter.delete('/projects/:id', async (c) => {
try {
const projectId = c.req.param('id');
const user = c.get('user');
// 检查项目是否存在并属于当前用户
const { data: existingProject, error: fetchError } = await supabase_1.default
.from('projects')
.select('*')
.eq('id', projectId)
.eq('created_by', user.id)
.single();
if (fetchError || !existingProject) {
return c.json({ error: 'Project not found or you do not have permission to delete it' }, 404);
}
// 删除项目
const { error } = await supabase_1.default
.from('projects')
.delete()
.eq('id', projectId);
if (error) {
console.error('Error deleting project:', error);
return c.json({ error: 'Failed to delete project' }, 500);
}
return c.json({
message: 'Project deleted successfully'
});
}
catch (error) {
console.error('Error deleting project:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 添加影响者到项目
communityRouter.post('/projects/:id/influencers', async (c) => {
try {
const projectId = c.req.param('id');
const { influencer_id, platform, external_id, name, profile_url } = await c.req.json();
const user = c.get('user');
// 检查项目是否存在并属于当前用户
const { data: existingProject, error: fetchError } = await supabase_1.default
.from('projects')
.select('*')
.eq('id', projectId)
.eq('created_by', user.id)
.single();
if (fetchError || !existingProject) {
return c.json({ error: 'Project not found or you do not have permission to update it' }, 404);
}
// 检查影响者是否已存在
let influencerData;
if (influencer_id) {
// 如果提供了影响者ID检查是否存在
const { data, error } = await supabase_1.default
.from('influencers')
.select('*')
.eq('influencer_id', influencer_id)
.single();
if (!error && data) {
influencerData = data;
}
}
else if (external_id && platform) {
// 如果提供了外部ID和平台检查是否存在
const { data, error } = await supabase_1.default
.from('influencers')
.select('*')
.eq('external_id', external_id)
.eq('platform', platform)
.single();
if (!error && data) {
influencerData = data;
}
}
// 如果影响者不存在,创建新的影响者
if (!influencerData) {
if (!name || !platform) {
return c.json({ error: 'Name and platform are required for new influencers' }, 400);
}
const { data, error } = await supabase_1.default
.from('influencers')
.insert({
name,
platform,
external_id,
profile_url
})
.select()
.single();
if (error) {
console.error('Error creating influencer:', error);
return c.json({ error: 'Failed to create influencer' }, 500);
}
influencerData = data;
}
// 将影响者添加到项目
const { data: projectInfluencer, error } = await supabase_1.default
.from('project_influencers')
.insert({
project_id: projectId,
influencer_id: influencerData.influencer_id
})
.select()
.single();
if (error) {
console.error('Error adding influencer to project:', error);
return c.json({ error: 'Failed to add influencer to project' }, 500);
}
return c.json({
message: 'Influencer added to project successfully',
project_influencer: projectInfluencer,
influencer: influencerData
}, 201);
}
catch (error) {
console.error('Error adding influencer to project:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 获取项目的影响者列表
communityRouter.get('/projects/:id/influencers', async (c) => {
try {
const projectId = c.req.param('id');
// 从Supabase获取项目的影响者列表
const { data, error } = await supabase_1.default
.from('project_influencers')
.select(`
project_id,
influencers (
influencer_id,
name,
platform,
profile_url,
external_id,
followers_count,
video_count
)
`)
.eq('project_id', projectId);
if (error) {
console.error('Error fetching project influencers:', error);
return c.json({ error: 'Failed to fetch project influencers' }, 500);
}
// 格式化数据
const influencers = data?.map(item => item.influencers) || [];
return c.json(influencers);
}
catch (error) {
console.error('Error fetching project influencers:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 从项目中移除影响者
communityRouter.delete('/projects/:projectId/influencers/:influencerId', async (c) => {
try {
const projectId = c.req.param('projectId');
const influencerId = c.req.param('influencerId');
const user = c.get('user');
// 检查项目是否存在并属于当前用户
const { data: existingProject, error: fetchError } = await supabase_1.default
.from('projects')
.select('*')
.eq('id', projectId)
.eq('created_by', user.id)
.single();
if (fetchError || !existingProject) {
return c.json({ error: 'Project not found or you do not have permission to update it' }, 404);
}
// 从项目中移除影响者
const { error } = await supabase_1.default
.from('project_influencers')
.delete()
.eq('project_id', projectId)
.eq('influencer_id', influencerId);
if (error) {
console.error('Error removing influencer from project:', error);
return c.json({ error: 'Failed to remove influencer from project' }, 500);
}
return c.json({
message: 'Influencer removed from project successfully'
});
}
catch (error) {
console.error('Error removing influencer from project:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 添加事件数据
communityRouter.post('/events', async (c) => {
try {
const { project_id, influencer_id, post_id, platform, event_type, metric_value, event_metadata } = await c.req.json();
if (!project_id || !influencer_id || !platform || !event_type || metric_value === undefined) {
return c.json({
error: 'Project ID, influencer ID, platform, event type, and metric value are required'
}, 400);
}
// 验证事件类型
const validEventTypes = [
'follower_change',
'post_like_change',
'post_view_change',
'click',
'comment',
'share'
];
if (!validEventTypes.includes(event_type)) {
return c.json({
error: `Invalid event type. Must be one of: ${validEventTypes.join(', ')}`
}, 400);
}
// 验证平台
const validPlatforms = ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'];
if (!validPlatforms.includes(platform)) {
return c.json({
error: `Invalid platform. Must be one of: ${validPlatforms.join(', ')}`
}, 400);
}
// 将事件数据插入ClickHouse
await clickhouse_1.default.query({
query: `
INSERT INTO events (
project_id,
influencer_id,
post_id,
platform,
event_type,
metric_value,
event_metadata
) VALUES (?, ?, ?, ?, ?, ?, ?)
`,
values: [
project_id,
influencer_id,
post_id || null,
platform,
event_type,
metric_value,
event_metadata ? JSON.stringify(event_metadata) : '{}'
]
});
return c.json({
message: 'Event data added successfully'
}, 201);
}
catch (error) {
console.error('Error adding event data:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 批量添加事件数据
communityRouter.post('/events/batch', async (c) => {
try {
const { events } = await c.req.json();
if (!Array.isArray(events) || events.length === 0) {
return c.json({ error: 'Events array is required and must not be empty' }, 400);
}
// 验证事件类型和平台
const validEventTypes = [
'follower_change',
'post_like_change',
'post_view_change',
'click',
'comment',
'share'
];
const validPlatforms = ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'];
// 验证每个事件
for (const event of events) {
const { project_id, influencer_id, platform, event_type, metric_value } = event;
if (!project_id || !influencer_id || !platform || !event_type || metric_value === undefined) {
return c.json({
error: 'Project ID, influencer ID, platform, event type, and metric value are required for all events'
}, 400);
}
if (!validEventTypes.includes(event_type)) {
return c.json({
error: `Invalid event type: ${event_type}. Must be one of: ${validEventTypes.join(', ')}`
}, 400);
}
if (!validPlatforms.includes(platform)) {
return c.json({
error: `Invalid platform: ${platform}. Must be one of: ${validPlatforms.join(', ')}`
}, 400);
}
}
// 准备批量插入数据
const values = events.map(event => `(
'${event.project_id}',
'${event.influencer_id}',
${event.post_id ? `'${event.post_id}'` : 'NULL'},
'${event.platform}',
'${event.event_type}',
${event.metric_value},
'${event.event_metadata ? JSON.stringify(event.event_metadata) : '{}'}'
)`).join(',');
// 批量插入事件数据
await clickhouse_1.default.query({
query: `
INSERT INTO events (
project_id,
influencer_id,
post_id,
platform,
event_type,
metric_value,
event_metadata
) VALUES ${values}
`
});
return c.json({
message: `${events.length} events added successfully`
}, 201);
}
catch (error) {
console.error('Error adding batch event data:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 添加帖子
communityRouter.post('/posts', async (c) => {
try {
const { influencer_id, platform, post_url, title, description, published_at } = await c.req.json();
if (!influencer_id || !platform || !post_url) {
return c.json({
error: 'Influencer ID, platform, and post URL are required'
}, 400);
}
// 验证平台
const validPlatforms = ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'];
if (!validPlatforms.includes(platform)) {
return c.json({
error: `Invalid platform. Must be one of: ${validPlatforms.join(', ')}`
}, 400);
}
// 检查帖子是否已存在
const { data: existingPost, error: checkError } = await supabase_1.default
.from('posts')
.select('*')
.eq('post_url', post_url)
.single();
if (!checkError && existingPost) {
return c.json({
error: 'Post with this URL already exists',
post: existingPost
}, 409);
}
// 创建新帖子
const { data, error } = await supabase_1.default
.from('posts')
.insert({
influencer_id,
platform,
post_url,
title,
description,
published_at: published_at || new Date().toISOString()
})
.select()
.single();
if (error) {
console.error('Error creating post:', error);
return c.json({ error: 'Failed to create post' }, 500);
}
return c.json({
message: 'Post created successfully',
post: data
}, 201);
}
catch (error) {
console.error('Error creating post:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 添加评论
communityRouter.post('/comments', async (c) => {
try {
const { post_id, user_id, content, sentiment_score } = await c.req.json();
if (!post_id || !content) {
return c.json({
error: 'Post ID and content are required'
}, 400);
}
// 创建新评论
const { data, error } = await supabase_1.default
.from('comments')
.insert({
post_id,
user_id: user_id || c.get('user').id,
content,
sentiment_score: sentiment_score || 0
})
.select()
.single();
if (error) {
console.error('Error creating comment:', error);
return c.json({ error: 'Failed to create comment' }, 500);
}
return c.json({
message: 'Comment created successfully',
comment: data
}, 201);
}
catch (error) {
console.error('Error creating comment:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 获取项目的事件统计
communityRouter.get('/projects/:id/event-stats', 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 = ?
GROUP BY event_type
ORDER BY event_count DESC
`,
values: [projectId]
});
// 提取数据
const statsData = 'rows' in result ? result.rows : [];
return c.json({
project_id: projectId,
event_stats: statsData
});
}
catch (error) {
console.error('Error fetching event stats:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 获取项目的时间趋势
communityRouter.get('/projects/:id/time-trend', async (c) => {
try {
const projectId = c.req.param('id');
const { event_type, interval = 'day', days = '30' } = c.req.query();
if (!event_type) {
return c.json({ error: 'Event type is required' }, 400);
}
// 验证事件类型
const validEventTypes = [
'follower_change',
'post_like_change',
'post_view_change',
'click',
'comment',
'share'
];
if (!validEventTypes.includes(event_type)) {
return c.json({
error: `Invalid event type. Must be one of: ${validEventTypes.join(', ')}`
}, 400);
}
// 验证时间间隔
const validIntervals = ['hour', 'day', 'week', 'month'];
if (!validIntervals.includes(interval)) {
return c.json({
error: `Invalid interval. Must be one of: ${validIntervals.join(', ')}`
}, 400);
}
// 构建时间间隔函数
let timeFunction;
switch (interval) {
case 'hour':
timeFunction = 'toStartOfHour';
break;
case 'day':
timeFunction = 'toDate';
break;
case 'week':
timeFunction = 'toStartOfWeek';
break;
case 'month':
timeFunction = 'toStartOfMonth';
break;
}
// 从ClickHouse查询项目的时间趋势
const result = await clickhouse_1.default.query({
query: `
SELECT
${timeFunction}(timestamp) AS time_period,
SUM(metric_value) AS value
FROM events
WHERE
project_id = ? AND
event_type = ? AND
timestamp >= subtractDays(now(), ?)
GROUP BY time_period
ORDER BY time_period ASC
`,
values: [projectId, event_type, parseInt(days)]
});
// 提取数据
const trendData = 'rows' in result ? result.rows : [];
return c.json({
project_id: projectId,
event_type,
interval,
days: parseInt(days),
trend: trendData
});
}
catch (error) {
console.error('Error fetching time trend:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
exports.default = communityRouter;