import { swaggerUI } from '@hono/swagger-ui' import { Hono } from 'hono' import supabase from '../utils/supabase' // 创建 OpenAPI 规范 export const openAPISpec = { openapi: '3.0.0', info: { title: 'Promote API', version: '1.0.0', description: 'API documentation for the Promote platform', }, servers: [ { url: 'http://localhost:4000', description: 'Local development server', }, ], paths: { '/': { get: { summary: 'Health check', description: 'Returns the API status', responses: { '200': { description: 'API is running', content: { 'application/json': { schema: { type: 'object', properties: { status: { type: 'string', example: 'ok' }, message: { type: 'string', example: 'Promote API is running' }, version: { type: 'string', example: '1.0.0' }, }, }, }, }, }, }, }, }, '/api/auth/register': { post: { summary: 'Register a new user', description: 'Creates a new user account', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['email', 'password', 'name'], properties: { email: { type: 'string', format: 'email', example: 'user@example.com' }, password: { type: 'string', format: 'password', example: 'securepassword' }, name: { type: 'string', example: 'John Doe' }, }, }, }, }, }, responses: { '201': { description: 'User registered successfully', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string', example: 'User registered successfully' }, user: { type: 'object', properties: { id: { type: 'string', example: '123e4567-e89b-12d3-a456-426614174000' }, email: { type: 'string', example: 'user@example.com' }, name: { type: 'string', example: 'John Doe' }, }, }, token: { type: 'string', example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' }, }, }, }, }, }, '400': { description: 'Bad request', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Email, password, and name are required' }, }, }, }, }, }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Internal server error' }, }, }, }, }, }, }, }, }, '/api/auth/login': { post: { summary: 'Login user', description: 'Authenticates a user and returns a JWT token', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['email', 'password'], properties: { email: { type: 'string', format: 'email', example: 'vitalitymailg@gmail.com' }, password: { type: 'string', format: 'password', example: 'password123' }, }, }, examples: { demoUser: { summary: '示例用户', value: { email: 'vitalitymailg@gmail.com', password: 'password123' } } } }, }, }, responses: { '200': { description: 'Login successful', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string', example: 'Login successful' }, user: { type: 'object', properties: { id: { type: 'string', example: '123e4567-e89b-12d3-a456-426614174000' }, email: { type: 'string', example: 'vitalitymailg@gmail.com' }, name: { type: 'string', example: 'Vitality User' }, }, }, token: { type: 'string', example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' }, }, }, }, }, }, '401': { description: 'Unauthorized', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Invalid credentials' }, }, }, }, }, }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Internal server error' }, }, }, }, }, }, }, }, }, '/api/auth/verify': { get: { summary: 'Verify token', description: 'Verifies a JWT token', security: [ { bearerAuth: [], }, ], responses: { '200': { description: 'Token is valid', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string', example: 'Token is valid' }, user: { type: 'object', properties: { id: { type: 'string', example: '123e4567-e89b-12d3-a456-426614174000' }, email: { type: 'string', example: 'vitalitymailg@gmail.com' }, }, }, }, }, }, }, }, '401': { description: 'Unauthorized', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Invalid token' }, }, }, }, }, }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Internal server error' }, }, }, }, }, }, }, }, }, '/api/analytics/view': { post: { summary: 'Track view event', description: 'Records a view event for content', security: [ { bearerAuth: [], }, ], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['content_id'], properties: { content_id: { type: 'string', example: 'content-123' }, }, }, }, }, }, responses: { '200': { description: 'View tracked successfully', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string', example: 'View tracked successfully' }, }, }, }, }, }, '400': { description: 'Bad request', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Content ID is required' }, }, }, }, }, }, '401': { description: 'Unauthorized', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Unauthorized: No token provided' }, }, }, }, }, }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Internal server error' }, }, }, }, }, }, }, }, }, '/api/analytics/like': { post: { summary: 'Track like event', description: 'Records a like or unlike event for content', security: [ { bearerAuth: [], }, ], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['content_id', 'action'], properties: { content_id: { type: 'string', example: 'content-123' }, action: { type: 'string', enum: ['like', 'unlike'], example: 'like' }, }, }, }, }, }, responses: { '200': { description: 'Like/unlike tracked successfully', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string', example: 'like tracked successfully' }, }, }, }, }, }, '400': { description: 'Bad request', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Content ID and action are required' }, }, }, }, }, }, '401': { description: 'Unauthorized', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Unauthorized: No token provided' }, }, }, }, }, }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Internal server error' }, }, }, }, }, }, }, }, }, '/api/analytics/follow': { post: { summary: 'Track follow event', description: 'Records a follow or unfollow event for a user', security: [ { bearerAuth: [], }, ], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['followed_id', 'action'], properties: { followed_id: { type: 'string', example: 'user-123' }, action: { type: 'string', enum: ['follow', 'unfollow'], example: 'follow' }, }, }, }, }, }, responses: { '200': { description: 'Follow/unfollow tracked successfully', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string', example: 'follow tracked successfully' }, }, }, }, }, }, '400': { description: 'Bad request', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Followed ID and action are required' }, }, }, }, }, }, '401': { description: 'Unauthorized', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Unauthorized: No token provided' }, }, }, }, }, }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Internal server error' }, }, }, }, }, }, }, }, }, '/api/analytics/content/{id}': { get: { summary: 'Get content analytics', description: 'Returns analytics data for a specific content', security: [ { bearerAuth: [], }, ], parameters: [ { name: 'id', in: 'path', required: true, schema: { type: 'string', }, description: 'Content ID', example: 'content-123', }, ], responses: { '200': { description: 'Content analytics data', content: { 'application/json': { schema: { type: 'object', properties: { content_id: { type: 'string', example: 'content-123' }, views: { type: 'integer', example: 1250 }, likes: { type: 'integer', example: 87 }, }, }, }, }, }, '401': { description: 'Unauthorized', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Unauthorized: No token provided' }, }, }, }, }, }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Internal server error' }, }, }, }, }, }, }, }, }, '/api/analytics/user/{id}': { get: { summary: 'Get user analytics', description: 'Returns analytics data for a specific user', security: [ { bearerAuth: [], }, ], parameters: [ { name: 'id', in: 'path', required: true, schema: { type: 'string', }, description: 'User ID', example: 'user-123', }, ], responses: { '200': { description: 'User analytics data', content: { 'application/json': { schema: { type: 'object', properties: { user_id: { type: 'string', example: 'user-123' }, followers: { type: 'integer', example: 542 }, content_analytics: { type: 'object', properties: { views: { type: 'array', items: { type: 'object', properties: { content_id: { type: 'string', example: 'content-123' }, view_count: { type: 'integer', example: 1250 }, }, }, }, likes: { type: 'array', items: { type: 'object', properties: { content_id: { type: 'string', example: 'content-123' }, like_count: { type: 'integer', example: 87 }, }, }, }, }, }, }, }, }, }, }, '401': { description: 'Unauthorized', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Unauthorized: No token provided' }, }, }, }, }, }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Internal server error' }, }, }, }, }, }, }, }, }, '/api/posts': { get: { summary: '获取帖子列表', description: '返回分页的帖子列表,支持过滤和排序', security: [{ bearerAuth: [] }], parameters: [ { name: 'influencer_id', in: 'query', description: '按影响者ID过滤', schema: { type: 'string', format: 'uuid' }, required: false }, { name: 'platform', in: 'query', description: '按平台过滤', schema: { type: 'string', enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] }, required: false }, { name: 'limit', in: 'query', description: '每页返回的记录数', schema: { type: 'integer', default: 20 }, required: false }, { name: 'offset', in: 'query', description: '分页偏移量', schema: { type: 'integer', default: 0 }, required: false }, { name: 'sort', in: 'query', description: '排序字段', schema: { type: 'string', default: 'published_at' }, required: false }, { name: 'order', in: 'query', description: '排序方向', schema: { type: 'string', enum: ['asc', 'desc'], default: 'desc' }, required: false } ], responses: { '200': { description: '成功获取帖子列表', content: { 'application/json': { schema: { type: 'object', properties: { posts: { type: 'array', items: { $ref: '#/components/schemas/Post' } }, total: { type: 'integer' }, limit: { type: 'integer' }, offset: { type: 'integer' } } } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } }, post: { summary: '创建新帖子', description: '创建一个新的帖子', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['influencer_id', 'platform', 'post_url'], properties: { influencer_id: { type: 'string', format: 'uuid' }, platform: { type: 'string', enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] }, post_url: { type: 'string', format: 'uri' }, title: { type: 'string' }, description: { type: 'string' }, published_at: { type: 'string', format: 'date-time' } } } } } }, responses: { '201': { description: '帖子创建成功', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' }, post: { $ref: '#/components/schemas/Post' } } } } } }, '400': { description: '请求参数错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '409': { description: '帖子URL已存在', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string' }, post: { $ref: '#/components/schemas/Post' } } } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/posts/{id}': { get: { summary: '获取单个帖子详情', description: '返回指定ID的帖子详情,包括统计数据和时间线', security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', description: '帖子ID', required: true, schema: { type: 'string', format: 'uuid' } } ], responses: { '200': { description: '成功获取帖子详情', content: { 'application/json': { schema: { $ref: '#/components/schemas/PostDetail' } } } }, '404': { description: '帖子不存在', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } }, put: { summary: '更新帖子', description: '更新指定ID的帖子信息', security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', description: '帖子ID', required: true, schema: { type: 'string', format: 'uuid' } } ], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { title: { type: 'string' }, description: { type: 'string' } } } } } }, responses: { '200': { description: '帖子更新成功', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' }, post: { $ref: '#/components/schemas/Post' } } } } } }, '404': { description: '帖子不存在', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } }, delete: { summary: '删除帖子', description: '删除指定ID的帖子', security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', description: '帖子ID', required: true, schema: { type: 'string', format: 'uuid' } } ], responses: { '200': { description: '帖子删除成功', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' } } } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/posts/{id}/comments': { get: { summary: '获取帖子评论', description: '返回指定帖子的评论列表', security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', description: '帖子ID', required: true, schema: { type: 'string', format: 'uuid' } }, { name: 'limit', in: 'query', description: '每页返回的记录数', schema: { type: 'integer', default: 20 }, required: false }, { name: 'offset', in: 'query', description: '分页偏移量', schema: { type: 'integer', default: 0 }, required: false } ], responses: { '200': { description: '成功获取评论列表', content: { 'application/json': { schema: { type: 'object', properties: { comments: { type: 'array', items: { $ref: '#/components/schemas/Comment' } }, total: { type: 'integer' }, limit: { type: 'integer' }, offset: { type: 'integer' } } } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } }, post: { summary: '添加评论', description: '为指定帖子添加新评论', security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', description: '帖子ID', required: true, schema: { type: 'string', format: 'uuid' } } ], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['content'], properties: { content: { type: 'string' }, sentiment_score: { type: 'number' } } } } } }, responses: { '201': { description: '评论添加成功', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' }, comment: { $ref: '#/components/schemas/Comment' } } } } } }, '400': { description: '请求参数错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/posts/comments/{id}': { put: { summary: '更新评论', description: '更新指定ID的评论', security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', description: '评论ID', required: true, schema: { type: 'string', format: 'uuid' } } ], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { content: { type: 'string' }, sentiment_score: { type: 'number' } } } } } }, responses: { '200': { description: '评论更新成功', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' }, comment: { $ref: '#/components/schemas/Comment' } } } } } }, '404': { description: '评论不存在或无权限', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } }, delete: { summary: '删除评论', description: '删除指定ID的评论', security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', description: '评论ID', required: true, schema: { type: 'string', format: 'uuid' } } ], responses: { '200': { description: '评论删除成功', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' } } } } } }, '404': { description: '评论不存在或无权限', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/comments': { get: { tags: ['Comments'], summary: 'Get comments', description: 'Retrieve a list of comments with optional filtering by post_id', parameters: [ { name: 'post_id', in: 'query', description: 'Filter comments by post ID', required: false, schema: { type: 'string', format: 'uuid' } }, { name: 'limit', in: 'query', description: 'Number of comments to return', required: false, schema: { type: 'integer', default: 10 } }, { name: 'offset', in: 'query', description: 'Number of comments to skip', required: false, schema: { type: 'integer', default: 0 } } ], responses: { '200': { description: 'List of comments', content: { 'application/json': { schema: { type: 'object', properties: { comments: { type: 'array', items: { $ref: '#/components/schemas/Comment' } }, count: { type: 'integer' }, limit: { type: 'integer' }, offset: { type: 'integer' } } } } } } } }, post: { tags: ['Comments'], summary: 'Create a comment', description: 'Create a new comment on a post', security: [ { bearerAuth: [] } ], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['post_id', 'content'], properties: { post_id: { type: 'string', format: 'uuid' }, content: { type: 'string' } } } } } }, responses: { '201': { description: 'Comment created successfully', content: { 'application/json': { schema: { $ref: '#/components/schemas/Comment' } } } }, '401': { description: 'Unauthorized' } } } }, '/api/comments/{comment_id}': { delete: { tags: ['Comments'], summary: 'Delete a comment', description: 'Delete a comment by ID (only for comment owner)', security: [ { bearerAuth: [] } ], parameters: [ { name: 'comment_id', in: 'path', description: 'Comment ID to delete', required: true, schema: { type: 'string', format: 'uuid' } } ], responses: { '204': { description: 'Comment deleted successfully' }, '401': { description: 'Unauthorized' }, '404': { description: 'Comment not found or unauthorized' } } } }, '/api/influencers': { get: { tags: ['Influencers'], summary: 'Get influencers', description: 'Retrieve a list of influencers with optional filtering and sorting', parameters: [ { name: 'platform', in: 'query', description: 'Filter by platform', required: false, schema: { type: 'string', enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] } }, { name: 'min_followers', in: 'query', description: 'Minimum number of followers', required: false, schema: { type: 'integer' } }, { name: 'max_followers', in: 'query', description: 'Maximum number of followers', required: false, schema: { type: 'integer' } }, { name: 'sort_by', in: 'query', description: 'Field to sort by', required: false, schema: { type: 'string', enum: ['followers_count', 'video_count', 'created_at'], default: 'followers_count' } }, { name: 'sort_order', in: 'query', description: 'Sort order', required: false, schema: { type: 'string', enum: ['asc', 'desc'], default: 'desc' } }, { name: 'limit', in: 'query', description: 'Number of influencers to return', required: false, schema: { type: 'integer', default: 10 } }, { name: 'offset', in: 'query', description: 'Number of influencers to skip', required: false, schema: { type: 'integer', default: 0 } } ], responses: { '200': { description: 'List of influencers', content: { 'application/json': { schema: { type: 'object', properties: { influencers: { type: 'array', items: { $ref: '#/components/schemas/Influencer' } }, count: { type: 'integer' }, limit: { type: 'integer' }, offset: { type: 'integer' } } } } } } } } }, '/api/influencers/stats': { get: { tags: ['Influencers'], summary: 'Get influencer statistics', description: 'Retrieve aggregated statistics about influencers', parameters: [ { name: 'platform', in: 'query', description: 'Filter statistics by platform', required: false, schema: { type: 'string', enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] } } ], responses: { '200': { description: 'Influencer statistics', content: { 'application/json': { schema: { type: 'object', properties: { total_influencers: { type: 'integer' }, total_followers: { type: 'integer' }, total_videos: { type: 'integer' }, average_followers: { type: 'integer' }, average_videos: { type: 'integer' } } } } } } } } }, '/api/influencers/{influencer_id}': { get: { tags: ['Influencers'], summary: 'Get influencer by ID', description: 'Retrieve detailed information about a specific influencer', parameters: [ { name: 'influencer_id', in: 'path', description: 'Influencer ID', required: true, schema: { type: 'string', format: 'uuid' } } ], responses: { '200': { description: 'Influencer details', content: { 'application/json': { schema: { $ref: '#/components/schemas/InfluencerWithPosts' } } } }, '404': { description: 'Influencer not found' } } } }, '/api/analytics/influencer/track': { post: { tags: ['Analytics'], summary: '追踪网红指标变化', description: '记录网红账号的关键指标(如粉丝数、视频数等)变化', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['influencer_id', 'metrics'], properties: { influencer_id: { type: 'string', format: 'uuid', description: '网红ID' }, metrics: { type: 'object', properties: { followers_count: { type: 'number', description: '粉丝数量' }, video_count: { type: 'number', description: '视频数量' }, views_count: { type: 'number', description: '总观看数' }, likes_count: { type: 'number', description: '总点赞数' } } } } } } } }, responses: { '200': { description: '成功追踪网红指标', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string', example: 'Influencer metrics tracked successfully' }, influencer_id: { type: 'string', format: 'uuid' }, tracked_metrics: { type: 'object', properties: { followers_count: { type: 'number' }, video_count: { type: 'number' } } } } } } } }, '400': { description: '请求参数错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '401': { description: '未授权', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: '服务器内部错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/analytics/influencer/{id}/growth': { get: { tags: ['Analytics'], summary: '获取网红粉丝增长趋势', description: '按不同时间粒度(天/周/月)获取网红的粉丝或其他指标变化趋势', security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' }, description: '网红ID' }, { name: 'metric', in: 'query', schema: { type: 'string', enum: ['followers_count', 'video_count', 'views_count', 'likes_count'], default: 'followers_count' }, description: '要分析的指标' }, { name: 'timeframe', in: 'query', schema: { type: 'string', enum: ['30days', '90days', '6months', '1year'], default: '6months' }, description: '分析的时间范围' }, { name: 'interval', in: 'query', schema: { type: 'string', enum: ['day', 'week', 'month'], default: 'month' }, description: '数据聚合的时间间隔' } ], responses: { '200': { description: '网红增长趋势数据', content: { 'application/json': { schema: { type: 'object', properties: { influencer_id: { type: 'string', format: 'uuid' }, influencer_info: { type: 'object', properties: { name: { type: 'string' }, platform: { type: 'string' }, followers_count: { type: 'number' } } }, metric: { type: 'string' }, timeframe: { type: 'string' }, interval: { type: 'string' }, data: { type: 'array', items: { type: 'object', properties: { time_period: { type: 'string', format: 'date' }, change: { type: 'number' }, total_value: { type: 'number' } } } } } } } } }, '401': { description: '未授权', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: '服务器内部错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/analytics/content/track': { post: { tags: ['Analytics'], summary: '追踪内容互动数据', description: '记录文章/内容的互动数据变化(如观看数、点赞数等)', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['post_id', 'metrics'], properties: { post_id: { type: 'string', format: 'uuid', description: '文章ID' }, metrics: { type: 'object', properties: { views_count: { type: 'number', description: '观看数量' }, likes_count: { type: 'number', description: '点赞数量' }, comments_count: { type: 'number', description: '评论数量' }, shares_count: { type: 'number', description: '分享数量' } } } } } } } }, responses: { '200': { description: '成功追踪内容指标', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string', example: 'Post metrics tracked successfully' }, post_id: { type: 'string', format: 'uuid' }, tracked_metrics: { type: 'object', properties: { views_count: { type: 'number' }, likes_count: { type: 'number' }, comments_count: { type: 'number' }, shares_count: { type: 'number' } } } } } } } }, '400': { description: '请求参数错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/analytics/content/{id}/trends': { get: { tags: ['Analytics'], summary: '获取内容互动趋势', description: '按不同时间粒度查看内容的互动数据变化趋势', security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' }, description: '文章ID' }, { name: 'metric', in: 'query', schema: { type: 'string', enum: ['views_count', 'likes_count', 'comments_count', 'shares_count'], default: 'views_count' }, description: '要分析的指标' }, { name: 'timeframe', in: 'query', schema: { type: 'string', enum: ['7days', '30days', '90days'], default: '30days' }, description: '分析的时间范围' }, { name: 'interval', in: 'query', schema: { type: 'string', enum: ['hour', 'day', 'week'], default: 'day' }, description: '数据聚合的时间间隔' } ], responses: { '200': { description: '内容互动趋势数据', content: { 'application/json': { schema: { type: 'object', properties: { post_id: { type: 'string', format: 'uuid' }, post_info: { type: 'object', properties: { title: { type: 'string' }, platform: { type: 'string' }, published_at: { type: 'string', format: 'date-time' } } }, metric: { type: 'string' }, timeframe: { type: 'string' }, interval: { type: 'string' }, data: { type: 'array', items: { type: 'object', properties: { time_period: { type: 'string', format: 'date' }, change: { type: 'number' }, total_value: { type: 'number' } } } } } } } } } } } }, '/api/analytics/project/{id}/overview': { get: { tags: ['Analytics'], summary: '获取项目整体分析', description: '获取项目的整体表现数据,包括关键指标、平台分布和时间线', security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' }, description: '项目ID' }, { name: 'timeframe', in: 'query', schema: { type: 'string', enum: ['7days', '30days', '90days', '6months'], default: '30days' }, description: '分析的时间范围' } ], responses: { '200': { description: '项目概览数据', content: { 'application/json': { schema: { type: 'object', properties: { project: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, name: { type: 'string' }, description: { type: 'string' }, created_at: { type: 'string', format: 'date-time' } } }, timeframe: { type: 'string' }, metrics: { type: 'object', properties: { total_influencers: { type: 'number' }, total_posts: { type: 'number' }, total_views: { type: 'number' }, total_likes: { type: 'number' }, total_comments: { type: 'number' }, total_shares: { type: 'number' }, total_followers: { type: 'number' } } }, platforms: { type: 'array', items: { type: 'object', properties: { platform: { type: 'string' }, count: { type: 'number' }, percentage: { type: 'number' } } } }, timeline: { type: 'array', items: { type: 'object', properties: { date: { type: 'string', format: 'date' }, views_change: { type: 'number' }, likes_change: { type: 'number' }, comments_change: { type: 'number' }, shares_change: { type: 'number' }, followers_change: { type: 'number' } } } } } } }, 'text/csv': { schema: { type: 'string' } } } } } } }, '/api/analytics/project/{id}/conversion-funnel': { get: { summary: '获取KOL合作转换漏斗数据', description: '获取项目中KOL合作的转换漏斗数据,包括各个阶段的数量和比率', tags: ['Analytics'], security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', required: true, description: '项目ID', schema: { type: 'string' } }, { name: 'timeframe', in: 'query', required: false, description: '时间范围 (7days, 30days, 90days, 6months)', schema: { type: 'string', enum: ['7days', '30days', '90days', '6months'], default: '30days' } } ], responses: { '200': { description: '成功获取KOL合作转换漏斗数据', content: { 'application/json': { schema: { type: 'object', properties: { project: { type: 'object', properties: { id: { type: 'string', description: '项目ID' }, name: { type: 'string', description: '项目名称' } } }, timeframe: { type: 'string', description: '时间范围' }, funnel_data: { type: 'array', description: '漏斗数据', items: { type: 'object', properties: { stage: { type: 'string', description: '阶段名称' }, count: { type: 'integer', description: 'KOL数量' }, rate: { type: 'integer', description: '占总数的百分比' } } } }, metrics: { type: 'object', properties: { total_influencers: { type: 'integer', description: 'KOL总数' }, conversion_rate: { type: 'integer', description: '总体转化率' }, avg_stage_dropoff: { type: 'integer', description: '平均阶段流失率' } } } } } } } }, '404': { description: '项目未找到', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Project not found' } } } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string', example: 'Internal server error' } } } } } } } } }, '/api/analytics/project/{id}/top-performers': { get: { tags: ['Analytics'], summary: '获取项目中表现最佳的网红', description: '获取项目中表现最佳的网红列表,可按不同指标排序', security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' }, description: '项目ID' }, { name: 'metric', in: 'query', schema: { type: 'string', enum: ['views_count', 'likes_count', 'followers_count', 'engagement_rate'], default: 'views_count' }, description: '排序指标' }, { name: 'limit', in: 'query', schema: { type: 'string', default: '10' }, description: '返回结果数量' }, { name: 'timeframe', in: 'query', schema: { type: 'string', enum: ['7days', '30days', '90days', '6months'], default: '30days' }, description: '分析的时间范围' } ], responses: { '200': { description: '表现最佳的网红列表', content: { 'application/json': { schema: { type: 'object', properties: { project_id: { type: 'string', format: 'uuid' }, metric: { type: 'string' }, timeframe: { type: 'string' }, top_performers: { type: 'array', items: { type: 'object', properties: { influencer_id: { type: 'string', format: 'uuid' }, name: { type: 'string' }, platform: { type: 'string' }, profile_url: { type: 'string' }, followers_count: { type: 'number' }, video_count: { type: 'number' }, views_count: { type: 'number' }, engagement_rate: { type: 'number' } } } } } } } } } } } }, '/api/analytics/schedule/influencer': { post: { tags: ['Analytics', 'Scheduled Collection'], summary: '调度网红数据采集', description: '设置定时采集网红指标数据的任务', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['influencer_id'], properties: { influencer_id: { type: 'string', format: 'uuid', description: '网红ID' }, cron_expression: { type: 'string', description: 'Cron表达式,默认为每天午夜执行 (0 0 * * *)', example: '0 0 * * *' } } } } } }, responses: { '200': { description: '成功调度数据采集', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' }, influencer_id: { type: 'string', format: 'uuid' }, cron_expression: { type: 'string' } } } } } } } } }, '/api/analytics/schedule/post': { post: { tags: ['Analytics', 'Scheduled Collection'], summary: '调度内容数据采集', description: '设置定时采集内容指标数据的任务', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['post_id'], properties: { post_id: { type: 'string', format: 'uuid', description: '文章ID' }, cron_expression: { type: 'string', description: 'Cron表达式,默认为每天午夜执行 (0 0 * * *)', example: '0 0 * * *' } } } } } }, responses: { '200': { description: '成功调度数据采集', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' }, post_id: { type: 'string', format: 'uuid' }, cron_expression: { type: 'string' } } } } } } } } }, '/api/analytics/schedule': { get: { tags: ['Analytics', 'Scheduled Collection'], summary: '获取所有调度任务', description: '获取所有已设置的定时数据采集任务', security: [{ bearerAuth: [] }], responses: { '200': { description: '调度任务列表', content: { 'application/json': { schema: { type: 'object', properties: { scheduled_jobs: { type: 'array', items: { type: 'object', properties: { id: { type: 'string' }, name: { type: 'string' }, pattern: { type: 'string' }, next: { type: 'string', format: 'date-time' } } } } } } } } } } } }, '/api/analytics/schedule/{job_id}': { delete: { tags: ['Analytics', 'Scheduled Collection'], summary: '删除调度任务', description: '删除一个已创建的定时数据采集任务', security: [{ bearerAuth: [] }], parameters: [ { name: 'job_id', in: 'path', required: true, schema: { type: 'string' }, description: '任务ID' } ], responses: { '200': { description: '成功删除任务', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' }, job_id: { type: 'string' } } } } } } } } }, '/api/analytics/export/influencer/{id}/growth': { get: { tags: ['Analytics', 'Data Export'], summary: '导出网红增长数据', description: '导出网红指标增长数据为CSV格式', security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' }, description: '网红ID' }, { name: 'metric', in: 'query', schema: { type: 'string', enum: ['followers_count', 'video_count', 'views_count', 'likes_count'], default: 'followers_count' }, description: '要导出的指标' }, { name: 'timeframe', in: 'query', schema: { type: 'string', enum: ['30days', '90days', '6months', '1year'], default: '6months' }, description: '导出的时间范围' }, { name: 'interval', in: 'query', schema: { type: 'string', enum: ['day', 'week', 'month'], default: 'month' }, description: '数据聚合的时间间隔' } ], responses: { '200': { description: 'CSV格式的网红增长数据', content: { 'text/csv': { schema: { type: 'string' } } } } } } }, '/api/analytics/export/project/{id}/performance': { get: { tags: ['Analytics', 'Data Export'], summary: '导出项目表现数据', description: '导出项目表现数据为CSV格式', security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' }, description: '项目ID' }, { name: 'timeframe', in: 'query', schema: { type: 'string', enum: ['7days', '30days', '90days', '6months'], default: '30days' }, description: '导出的时间范围' } ], responses: { '200': { description: 'CSV格式的项目表现数据', content: { 'text/csv': { schema: { type: 'string' } } } } } } }, '/api/analytics/reports/project/{id}': { get: { tags: ['Analytics', 'Reports'], summary: '生成项目报告', description: '生成项目表现的详细报告', security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' }, description: '项目ID' }, { name: 'timeframe', in: 'query', schema: { type: 'string', enum: ['7days', '30days', '90days', '6months'], default: '30days' }, description: '报告的时间范围' }, { name: 'format', in: 'query', schema: { type: 'string', enum: ['json', 'csv'], default: 'json' }, description: '报告格式' } ], responses: { '200': { description: '项目报告', content: { 'application/json': { schema: { type: 'object', properties: { report_type: { type: 'string' }, generated_at: { type: 'string', format: 'date-time' }, timeframe: { type: 'string' }, project: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, name: { type: 'string' }, description: { type: 'string' } } }, summary: { type: 'object', properties: { total_influencers: { type: 'number' }, total_posts: { type: 'number' }, total_views_gain: { type: 'number' }, total_likes_gain: { type: 'number' }, total_followers_gain: { type: 'number' } } }, top_influencers: { type: 'array', items: { type: 'object', properties: { influencer_id: { type: 'string', format: 'uuid' }, name: { type: 'string' }, platform: { type: 'string' }, followers_count: { type: 'number' }, total_views_gain: { type: 'number' } } } }, top_posts: { type: 'array', items: { type: 'object', properties: { post_id: { type: 'string', format: 'uuid' }, title: { type: 'string' }, platform: { type: 'string' }, published_at: { type: 'string', format: 'date-time' }, influencer_name: { type: 'string' }, views_count: { type: 'number' }, likes_count: { type: 'number' }, engagement_rate: { type: 'number' } } } } } } }, 'text/csv': { schema: { type: 'string' } } } } } } }, }, components: { schemas: { Post: { type: 'object', properties: { post_id: { type: 'string', format: 'uuid' }, influencer_id: { type: 'string', format: 'uuid' }, platform: { type: 'string', enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] }, post_url: { type: 'string', format: 'uri' }, title: { type: 'string' }, description: { type: 'string' }, published_at: { type: 'string', format: 'date-time' }, created_at: { type: 'string', format: 'date-time' }, updated_at: { type: 'string', format: 'date-time' }, influencer: { type: 'object', properties: { name: { type: 'string' }, platform: { type: 'string' }, profile_url: { type: 'string' }, followers_count: { type: 'integer' } } }, stats: { type: 'object', properties: { views: { type: 'integer' }, likes: { type: 'integer' } } } } }, PostDetail: { type: 'object', properties: { post_id: { type: 'string', format: 'uuid' }, influencer_id: { type: 'string', format: 'uuid' }, platform: { type: 'string', enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] }, post_url: { type: 'string', format: 'uri' }, title: { type: 'string' }, description: { type: 'string' }, published_at: { type: 'string', format: 'date-time' }, created_at: { type: 'string', format: 'date-time' }, updated_at: { type: 'string', format: 'date-time' }, influencer: { type: 'object', properties: { name: { type: 'string' }, platform: { type: 'string' }, profile_url: { type: 'string' }, followers_count: { type: 'integer' } } }, stats: { type: 'object', properties: { views: { type: 'integer' }, likes: { type: 'integer' } } }, timeline: { type: 'array', items: { type: 'object', properties: { date: { type: 'string', format: 'date' }, event_type: { type: 'string' }, value: { type: 'integer' } } } }, comment_count: { type: 'integer' } } }, Comment: { type: 'object', properties: { comment_id: { type: 'string', format: 'uuid' }, content: { type: 'string' }, sentiment_score: { type: 'number', description: '情感分析分数,范围从 -1 到 1' }, created_at: { type: 'string', format: 'date-time' }, updated_at: { type: 'string', format: 'date-time' }, post_id: { type: 'string', format: 'uuid' }, user_id: { type: 'string', format: 'uuid' }, user_profile: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, full_name: { type: 'string' }, avatar_url: { type: 'string', format: 'uri' } } }, post: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, title: { type: 'string' }, platform: { type: 'string', enum: ['facebook', 'threads', 'instagram', 'linkedin', 'xiaohongshu', 'youtube'] }, content_type: { type: 'string', enum: ['post', 'reel', 'video', 'short'] }, influencer: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, name: { type: 'string' }, type: { type: 'string', enum: ['user', 'kol', 'official'] } } } } }, status: { type: 'string', enum: ['pending', 'approved', 'rejected'] }, reply_status: { type: 'string', enum: ['none', 'draft', 'sent'] }, language: { type: 'string', enum: ['zh-TW', 'zh-CN', 'en'] } } }, Influencer: { type: 'object', properties: { influencer_id: { type: 'string', format: 'uuid' }, name: { type: 'string' }, platform: { type: 'string', enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] }, profile_url: { type: 'string' }, followers_count: { type: 'integer' }, video_count: { type: 'integer' }, platform_count: { type: 'integer' }, created_at: { type: 'string', format: 'date-time' }, updated_at: { type: 'string', format: 'date-time' } } }, InfluencerWithPosts: { allOf: [ { $ref: '#/components/schemas/Influencer' }, { type: 'object', properties: { posts: { type: 'array', items: { type: 'object', properties: { post_id: { type: 'string', format: 'uuid' }, title: { type: 'string' }, description: { type: 'string' }, published_at: { type: 'string', format: 'date-time' } } } } } } ] }, Error: { type: 'object', properties: { error: { type: 'string' } } } }, securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', description: '登录后获取的 token 可直接在此处使用。在 Authorize 按钮中输入 "Bearer your-token" 或直接输入 token(不带 Bearer 前缀)。' }, }, }, } // 创建 Swagger UI 路由 export const createSwaggerUI = () => { const app = new Hono() // 设置 Swagger UI 路由 app.get('/swagger', swaggerUI({ url: '/api/swagger.json', })) // 提供 OpenAPI 规范的 JSON 端点 app.get('/api/swagger.json', (c) => { return c.json(openAPISpec) }) // 添加临时的 token 生成端点,仅用于 Swagger 测试 app.get('/api/swagger/token', async (c) => { try { // Swagger 测试用户的凭据 const email = 'swagger@test.com'; const password = 'swagger-test-password'; // 尝试使用 Supabase 认证 const { data, error } = await supabase.auth.signInWithPassword({ email, password }); if (error || !data.session) { // 如果登录失败,可能需要先创建测试用户 console.log('尝试创建 Swagger 测试用户...'); // 尝试创建测试用户 await supabase.auth.admin.createUser({ email, password, email_confirm: true, }); // 再次尝试登录 const loginResult = await supabase.auth.signInWithPassword({ email, password }); if (loginResult.error || !loginResult.data.session) { return c.json({ error: '无法创建测试用户凭据', details: loginResult.error?.message }, 500); } return c.json({ message: '已创建 Swagger 测试用户并生成 token', token: loginResult.data.session.access_token, usage: '在 Authorize 对话框中输入: Bearer [token]' }); } return c.json({ message: '此 token 仅用于 Swagger UI 测试', token: data.session.access_token, usage: '在 Authorize 对话框中输入: Bearer [token]' }); } catch (error) { console.error('Error generating swagger token:', error); return c.json({ error: '生成 token 失败', details: error instanceof Error ? error.message : String(error) }, 500); } }); return app }