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', }, ], tags: [ { name: 'Core', description: 'Core API endpoints' }, { name: 'Influencers', description: 'Influencer management endpoints' }, { name: 'Posts', description: 'Post management endpoints' }, { name: 'Comments', description: 'Comment management endpoints' }, { name: 'Projects', description: 'Project management endpoints' }, { name: 'Analytics', description: 'Analytics and reporting endpoints' } ], paths: { '/': { get: { summary: 'Health check', description: 'Returns the API status', tags: ['Core'], 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', tags: ['Core'], 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', tags: ['Core'], 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', tags: ['Core'], 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/admin/settings': { post: { summary: 'Update admin settings', description: 'Updates the admin settings', tags: ['Core'], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['settings'], properties: { settings: { type: 'object', properties: { // Add any other settings you want to update here } } } } } } }, responses: { '200': { description: 'Settings updated successfully', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' } } } } } }, '400': { description: 'Bad request', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/posts': { get: { summary: 'Get all posts', description: 'Returns a list of posts with pagination and filtering options', tags: ['Posts'], 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: 'Create a new post', description: 'Creates a new post in the system', tags: ['Posts'], 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/{post_id}': { get: { summary: 'Get post by ID', description: 'Returns a specific post by ID', tags: ['Posts'], parameters: [ { name: 'post_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: 'Update post', description: 'Updates an existing post', tags: ['Posts'], parameters: [ { name: 'post_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: 'Delete post', description: 'Removes a post from the system', tags: ['Posts'], parameters: [ { name: 'post_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/{post_id}/comments': { get: { summary: 'Get comments for post', description: 'Returns all comments associated with a specific post', tags: ['Posts', 'Comments'], parameters: [ { name: 'post_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: 'Add comment', description: 'Adds a new comment to a specific post', tags: ['Posts', 'Comments'], security: [{ bearerAuth: [] }], parameters: [ { name: 'post_id', in: 'path', description: 'Post 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: 'Comment added successfully', 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: 'Update comment', description: 'Updates a specific comment', tags: ['Posts', 'Comments'], security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', description: 'Comment 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: 'Comment updated successfully', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' }, comment: { $ref: '#/components/schemas/Comment' } } } } } }, '404': { description: 'Comment not found or no permission', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } }, delete: { summary: 'Delete comment', description: 'Deletes a specific comment', tags: ['Posts', 'Comments'], security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', description: 'Comment ID', required: true, schema: { type: 'string', format: 'uuid' } } ], responses: { '200': { description: 'Comment deleted successfully', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' } } } } } }, '404': { description: 'Comment not found or no permission', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/comments': { get: { summary: 'Get all comments', description: 'Returns a list of comments with pagination and filtering options', tags: ['Comments'], 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: { summary: 'Create a new comment', description: 'Create a new comment on a post', tags: ['Comments'], 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}': { get: { summary: 'Get comment by ID', description: 'Returns a specific comment by ID', tags: ['Comments'], parameters: [ { name: 'comment_id', in: 'path', description: 'Comment ID to get', required: true, schema: { type: 'string', format: 'uuid' } } ], responses: { '200': { description: 'Comment found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Comment' } } } }, '404': { description: 'Comment not found' } } }, put: { summary: 'Update comment', description: 'Updates an existing comment', tags: ['Comments'], parameters: [ { name: 'comment_id', in: 'path', description: 'Comment ID to update', 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: { '200': { description: 'Comment updated successfully', content: { 'application/json': { schema: { $ref: '#/components/schemas/Comment' } } } }, '404': { description: 'Comment not found' }, '500': { description: 'Internal server error' } } }, delete: { summary: 'Delete comment', description: 'Removes a comment from the system', tags: ['Comments'], parameters: [ { name: 'comment_id', in: 'path', description: 'Comment ID to delete', required: true, schema: { type: 'string', format: 'uuid' } } ], responses: { '200': { description: 'Comment deleted successfully', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' } } } } } }, '404': { description: 'Comment not found' }, '500': { description: 'Internal server error' } } } }, '/api/influencers': { get: { summary: 'Get all influencers', description: 'Returns a list of influencers with pagination and filtering options', tags: ['Influencers'], 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' } } } } } } } }, post: { summary: 'Create a new influencer', description: 'Creates a new influencer in the system', tags: ['Influencers'], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['name', 'platform', 'profile_url', 'followers_count', 'video_count'], properties: { name: { type: 'string' }, platform: { type: 'string' }, profile_url: { type: 'string' }, followers_count: { type: 'integer' }, video_count: { type: 'integer' } } } } } }, responses: { '201': { description: 'Influencer created successfully', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' }, influencer: { $ref: '#/components/schemas/Influencer' } } } } } }, '400': { description: 'Bad request', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/influencers/stats': { get: { summary: 'Get influencer statistics', description: 'Returns aggregate statistics about influencers in the system', tags: ['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: { summary: 'Get influencer by ID', description: 'Returns a specific influencer by ID', tags: ['Influencers'], 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' } } }, put: { summary: 'Update influencer', description: 'Updates an existing influencer', tags: ['Influencers'], parameters: [ { name: 'influencer_id', in: 'path', description: 'Influencer ID', required: true, schema: { type: 'string', format: 'uuid' } } ], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['name', 'platform', 'profile_url', 'followers_count', 'video_count'], properties: { name: { type: 'string' }, platform: { type: 'string' }, profile_url: { type: 'string' }, followers_count: { type: 'integer' }, video_count: { type: 'integer' } } } } } }, responses: { '200': { description: 'Influencer updated successfully', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' }, influencer: { $ref: '#/components/schemas/Influencer' } } } } } }, '404': { description: 'Influencer not found' }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } }, delete: { summary: 'Delete influencer', description: 'Removes an influencer from the system', tags: ['Influencers'], parameters: [ { name: 'influencer_id', in: 'path', description: 'Influencer ID', required: true, schema: { type: 'string', format: 'uuid' } } ], responses: { '200': { description: 'Influencer deleted successfully', content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' } } } } } }, '404': { description: 'Influencer not found' }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/influencers/{influencer_id}/posts': { get: { summary: 'Get posts by influencer', description: 'Returns all posts associated with a specific influencer', tags: ['Influencers', 'Posts'], parameters: [ { name: 'influencer_id', in: 'path', description: 'Influencer ID', required: true, schema: { type: 'string', format: 'uuid' } } ], responses: { '200': { description: 'Posts by influencer', content: { 'application/json': { schema: { type: 'object', properties: { posts: { type: 'array', items: { $ref: '#/components/schemas/Post' } }, total: { type: 'integer' } } } } } }, '404': { description: 'Influencer not found' }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/projects': { get: { summary: 'Get all projects', description: 'Returns a list of projects with pagination and filtering options', tags: ['Projects'], parameters: [ { name: 'created_by', in: 'query', description: 'Filter by creator user ID', required: false, schema: { type: 'string', format: 'uuid' } }, { name: 'limit', in: 'query', description: 'Number of projects to return', required: false, schema: { type: 'integer', default: 20 } }, { name: 'offset', in: 'query', description: 'Number of projects to skip', required: false, schema: { type: 'integer', default: 0 } } ], responses: { '200': { description: 'List of projects', content: { 'application/json': { schema: { type: 'object', properties: { projects: { type: 'array', items: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, name: { type: 'string' }, description: { type: 'string' }, created_by: { type: 'string', format: 'uuid' }, created_at: { type: 'string', format: 'date-time' }, updated_at: { type: 'string', format: 'date-time' } } } }, count: { type: 'integer' }, limit: { type: 'integer' }, offset: { type: 'integer' } } } } } }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } }, post: { summary: 'Create a new project', description: 'Creates a new project in the system', tags: ['Projects'], security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['name'], properties: { name: { type: 'string' }, description: { type: 'string' } } } } } }, responses: { '201': { description: 'Project created successfully', content: { 'application/json': { schema: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, name: { type: 'string' }, description: { type: 'string' }, created_by: { type: 'string', format: 'uuid' }, created_at: { type: 'string', format: 'date-time' }, updated_at: { type: 'string', format: 'date-time' } } } } } }, '400': { description: 'Bad request', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/projects/{id}': { get: { summary: 'Get project by ID', description: 'Returns a specific project by ID', tags: ['Projects'], parameters: [ { name: 'id', in: 'path', description: 'Project ID', required: true, schema: { type: 'string', format: 'uuid' } } ], responses: { '200': { description: 'Project details', content: { 'application/json': { schema: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, name: { type: 'string' }, description: { type: 'string' }, created_by: { type: 'string', format: 'uuid' }, created_at: { type: 'string', format: 'date-time' }, updated_at: { type: 'string', format: 'date-time' } } } } } }, '404': { description: 'Project not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } }, put: { summary: 'Update project', description: 'Updates an existing project', tags: ['Projects'], security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', description: 'Project ID', required: true, schema: { type: 'string', format: 'uuid' } } ], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { name: { type: 'string' }, description: { type: 'string' } } } } } }, responses: { '200': { description: 'Project updated successfully', content: { 'application/json': { schema: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, name: { type: 'string' }, description: { type: 'string' }, created_by: { type: 'string', format: 'uuid' }, created_at: { type: 'string', format: 'date-time' }, updated_at: { type: 'string', format: 'date-time' } } } } } }, '404': { description: 'Project not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } }, delete: { summary: 'Delete project', description: 'Removes a project from the system', tags: ['Projects'], security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', description: 'Project ID', required: true, schema: { type: 'string', format: 'uuid' } } ], responses: { '204': { description: 'Project deleted successfully' }, '404': { description: 'Project not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/projects/{id}/influencers': { get: { summary: 'Get project influencers', description: 'Returns all influencers associated with a specific project', tags: ['Projects', 'Influencers'], parameters: [ { name: 'id', in: 'path', description: 'Project ID', required: true, schema: { type: 'string', format: 'uuid' } } ], responses: { '200': { description: 'List of influencers in the project', content: { 'application/json': { schema: { type: 'object', properties: { influencers: { type: 'array', items: { $ref: '#/components/schemas/Influencer' } }, count: { type: 'integer' } } } } } }, '404': { description: 'Project not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } }, post: { summary: 'Add influencer to project', description: 'Adds an influencer to a project', tags: ['Projects', 'Influencers'], security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', description: 'Project ID', required: true, schema: { type: 'string', format: 'uuid' } } ], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['influencer_id'], properties: { influencer_id: { type: 'string', format: 'uuid' } } } } } }, responses: { '201': { description: 'Influencer added to project successfully', content: { 'application/json': { schema: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, project_id: { type: 'string', format: 'uuid' }, influencer_id: { type: 'string', format: 'uuid' }, created_at: { type: 'string', format: 'date-time' } } } } } }, '400': { description: 'Bad request', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '404': { description: 'Project or influencer not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '409': { description: 'Influencer already in project', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/projects/{id}/influencers/{influencer_id}': { delete: { summary: 'Remove influencer from project', description: 'Removes an influencer from a project', tags: ['Projects', 'Influencers'], security: [{ bearerAuth: [] }], parameters: [ { name: 'id', in: 'path', description: 'Project ID', required: true, schema: { type: 'string', format: 'uuid' } }, { name: 'influencer_id', in: 'path', description: 'Influencer ID', required: true, schema: { type: 'string', format: 'uuid' } } ], responses: { '204': { description: 'Influencer removed from project successfully' }, '404': { description: 'Project, influencer, or relationship not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/analytics/kol-overview': { get: { summary: 'Get KOL performance overview', description: 'Returns card-style layout showing key performance metrics for each KOL including followers growth, new likes, and new follows', tags: ['Analytics'], parameters: [ { name: 'timeRange', in: 'query', description: 'Number of days to look back', schema: { type: 'string', enum: ['7', '30', '90'], default: '30' } }, { name: 'projectId', in: 'query', description: 'Filter by project ID', schema: { type: 'string' } }, { name: 'sortBy', in: 'query', description: 'Field to sort by', schema: { type: 'string', enum: ['followers_change', 'likes_change', 'follows_change'], default: 'followers_change' } }, { name: 'sortOrder', in: 'query', description: 'Sort order', schema: { type: 'string', enum: ['asc', 'desc'], default: 'desc' } }, { name: 'limit', in: 'query', description: 'Number of KOLs to return', schema: { type: 'integer', default: 20 } }, { name: 'offset', in: 'query', description: 'Offset for pagination', schema: { type: 'integer', default: 0 } } ], responses: { '200': { description: 'Successful response with KOL performance data', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: true }, data: { type: 'array', items: { type: 'object', properties: { influencer_id: { type: 'string', example: 'inf-123-456' }, name: { type: 'string', example: 'John Influencer' }, platform: { type: 'string', example: 'instagram' }, profile_url: { type: 'string', example: 'https://instagram.com/johninfluencer' }, followers_count: { type: 'integer', example: 50000 }, followers_change: { type: 'integer', example: 1500 }, followers_change_percentage: { type: 'number', nullable: true, example: 12.5 }, likes_change: { type: 'integer', example: 2800 }, likes_change_percentage: { type: 'number', nullable: true, example: 15.3 }, follows_change: { type: 'integer', example: 1200 }, follows_change_percentage: { type: 'number', nullable: true, example: 8.7 } } } }, pagination: { type: 'object', properties: { limit: { type: 'integer', example: 20 }, offset: { type: 'integer', example: 0 }, total: { type: 'integer', example: 42 } } } } } } } }, '400': { description: 'Bad request - invalid parameters', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Invalid sortBy. Must be one of: followers_change, likes_change, follows_change' } } } } } }, '500': { description: 'Server error', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Failed to fetch KOL overview data' }, message: { type: 'string', example: 'ClickHouse query error: Connection refused' } } } } } } } } }, '/api/analytics/kol-funnel': { get: { summary: 'Get KOL conversion funnel data', description: 'Returns user counts and conversion rates for each funnel stage in the KOL collaboration process', tags: ['Analytics'], parameters: [ { name: 'timeRange', in: 'query', description: 'Number of days to look back', schema: { type: 'string', enum: ['7', '30', '90'], default: '30' } }, { name: 'projectId', in: 'query', description: 'Filter by project ID', schema: { type: 'string' } }, { name: 'debug', in: 'query', description: 'Enable debug mode (non-production environments only)', schema: { type: 'string', enum: ['true', 'false'], default: 'false' } } ], responses: { '200': { description: 'Successful response with funnel data', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: true }, data: { type: 'object', properties: { stages: { type: 'array', items: { type: 'object', properties: { stage: { type: 'string', example: 'exposure' }, stage_display: { type: 'string', example: '曝光' }, count: { type: 'integer', example: 10000 }, percentage: { type: 'number', example: 100 }, conversion_rate: { type: 'number', nullable: true, example: 75.5 } } } }, overview: { type: 'object', properties: { average_conversion_rate: { type: 'number', example: 45.2 }, highest_conversion_stage: { type: 'string', example: '兴趣' }, lowest_conversion_stage: { type: 'string', example: '购买' } } } } } } } } } }, '400': { description: 'Bad request - invalid parameters', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Invalid timeRange. Must be 7, 30, or 90.' } } } } } }, '500': { description: 'Server error', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Failed to fetch KOL funnel data' }, message: { type: 'string', example: 'ClickHouse query error: Connection refused' } } } } } } } } }, '/api/analytics/post-performance': { get: { summary: 'Get KOL post performance data', description: 'Returns table data of posts with key metrics including views, likes, comments, shares and sentiment scores', tags: ['Analytics'], parameters: [ { name: 'kolId', in: 'query', description: 'Filter by KOL ID', schema: { type: 'string' } }, { name: 'platform', in: 'query', description: 'Filter by platform', schema: { type: 'string', enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] } }, { name: 'startDate', in: 'query', description: 'Start date filter (YYYY-MM-DD)', schema: { type: 'string', format: 'date' } }, { name: 'endDate', in: 'query', description: 'End date filter (YYYY-MM-DD)', schema: { type: 'string', format: 'date' } }, { name: 'sortBy', in: 'query', description: 'Field to sort by', schema: { type: 'string', enum: ['publish_date', 'views', 'likes', 'comments', 'shares', 'sentiment_score'], default: 'publish_date' } }, { name: 'sortOrder', in: 'query', description: 'Sort order', schema: { type: 'string', enum: ['asc', 'desc'], default: 'desc' } }, { name: 'limit', in: 'query', description: 'Number of posts to return', schema: { type: 'integer', default: 20 } }, { name: 'offset', in: 'query', description: 'Offset for pagination', schema: { type: 'integer', default: 0 } } ], responses: { '200': { description: 'Successful response with post performance data', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: true }, data: { type: 'array', items: { type: 'object', properties: { post_id: { type: 'string', example: 'post-123' }, title: { type: 'string', example: '夏季新品分享' }, kol_id: { type: 'string', example: 'kol-456' }, kol_name: { type: 'string', example: '時尚達人' }, platform: { type: 'string', example: 'instagram' }, publish_date: { type: 'string', format: 'date-time', example: '2023-06-15T08:30:00Z' }, metrics: { type: 'object', properties: { views: { type: 'integer', example: 15000 }, likes: { type: 'integer', example: 1200 }, comments: { type: 'integer', example: 85 }, shares: { type: 'integer', example: 45 } } }, sentiment_score: { type: 'number', example: 0.75 }, post_url: { type: 'string', format: 'uri', example: 'https://instagram.com/p/abc123' } } } }, pagination: { type: 'object', properties: { limit: { type: 'integer', example: 20 }, offset: { type: 'integer', example: 0 }, total: { type: 'integer', example: 156 } } }, is_mock_data: { type: 'boolean', description: '标识返回的是否是模拟数据', example: false } } } } } }, '400': { description: 'Bad request - invalid parameters', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Invalid sortBy. Must be one of: publish_date, views, likes, comments, shares, sentiment_score' } } } } } }, '500': { description: 'Server error', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Failed to fetch post performance data' }, message: { type: 'string', example: 'ClickHouse query error: Connection refused' } } } } } } } } }, '/api/analytics/dashboard-cards': { get: { summary: '获取概览卡片数据', description: '返回包含留言总数、平均互动率和情感分析三个核心指标的卡片数据及环比变化', tags: ['Analytics'], parameters: [ { name: 'timeRange', in: 'query', description: '时间范围(天)', schema: { type: 'string', enum: ['7', '30', '90'], default: '30' } }, { name: 'projectId', in: 'query', description: '项目ID过滤', schema: { type: 'string' } } ], responses: { '200': { description: '成功响应', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: true }, data: { type: 'object', properties: { comments_count: { type: 'object', properties: { current: { type: 'number', example: 256 }, change_percentage: { type: 'number', example: 12.5 } } }, engagement_rate: { type: 'object', properties: { current: { type: 'number', example: 5.8 }, change_percentage: { type: 'number', example: -2.3 } } }, sentiment_score: { type: 'object', properties: { current: { type: 'number', example: 0.75 }, change_percentage: { type: 'number', example: 8.7 } } } } } } } } } }, '400': { description: '参数错误', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Invalid timeRange. Must be 7, 30, or 90.' } } } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Failed to fetch dashboard card data' }, message: { type: 'string', example: 'Internal server error' } } } } } } } } }, '/api/analytics/comment-trend': { get: { summary: '获取留言趋势数据', description: '返回一段时间内留言数量的变化趋势,用于绘制柱状图', tags: ['Analytics'], parameters: [ { name: 'timeRange', in: 'query', description: '时间范围(天)', schema: { type: 'string', enum: ['7', '30', '90'], default: '30' } }, { name: 'projectId', in: 'query', description: '项目ID过滤', schema: { type: 'string' } }, { name: 'platform', in: 'query', description: '平台过滤', schema: { type: 'string', enum: ['Twitter', 'Instagram', 'TikTok', 'Facebook', 'YouTube'] } } ], responses: { '200': { description: '成功响应', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: true }, data: { type: 'array', items: { type: 'object', properties: { date: { type: 'string', format: 'date', example: '2025-03-01' }, count: { type: 'number', example: 32 } } } }, metadata: { type: 'object', properties: { max_count: { type: 'number', example: 58 }, total_count: { type: 'number', example: 256 } } } } } } } }, '400': { description: '参数错误', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Invalid timeRange. Must be 7, 30, or 90.' } } } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Failed to fetch comment trend data' }, message: { type: 'string', example: 'Internal server error' } } } } } } } } }, '/api/analytics/platform-distribution': { get: { summary: '获取平台分布数据', description: '返回不同社交平台上的评论或互动分布情况,用于绘制柱状图', tags: ['Analytics'], parameters: [ { name: 'timeRange', in: 'query', description: '时间范围(天)', schema: { type: 'string', enum: ['7', '30', '90'], default: '30' } }, { name: 'projectId', in: 'query', description: '项目ID过滤', schema: { type: 'string' } }, { name: 'eventType', in: 'query', description: '事件类型', schema: { type: 'string', enum: ['comment', 'like', 'view', 'share'], default: 'comment' } } ], responses: { '200': { description: '成功响应', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: true }, data: { type: 'array', items: { type: 'object', properties: { platform: { type: 'string', example: 'Instagram' }, count: { type: 'number', example: 128 }, percentage: { type: 'number', example: 42.5 } } } }, metadata: { type: 'object', properties: { total: { type: 'number', example: 312 }, event_type: { type: 'string', example: 'comment' } } } } } } } }, '400': { description: '参数错误', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Invalid eventType. Must be one of: comment, like, view, share' } } } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Failed to fetch platform distribution data' }, message: { type: 'string', example: 'Internal server error' } } } } } } } } }, '/api/analytics/sentiment-analysis': { get: { summary: '获取情感分析详情', description: '返回正面、中性、负面评论的比例和平均情感得分', tags: ['Analytics'], parameters: [ { name: 'timeRange', in: 'query', description: '时间范围(天)', schema: { type: 'string', enum: ['7', '30', '90'], default: '30' } }, { name: 'projectId', in: 'query', description: '项目ID过滤', schema: { type: 'string' } }, { name: 'platform', in: 'query', description: '平台过滤', schema: { type: 'string', enum: ['Twitter', 'Instagram', 'TikTok', 'Facebook', 'YouTube'] } } ], responses: { '200': { description: '成功响应', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: true }, data: { type: 'object', properties: { positive: { type: 'object', properties: { count: { type: 'number', example: 156 }, percentage: { type: 'number', example: 65.2 } } }, neutral: { type: 'object', properties: { count: { type: 'number', example: 45 }, percentage: { type: 'number', example: 20.8 } } }, negative: { type: 'object', properties: { count: { type: 'number', example: 28 }, percentage: { type: 'number', example: 14.0 } } }, total: { type: 'number', example: 229 }, average_score: { type: 'number', example: 0.68 } } } } } } } }, '400': { description: '参数错误', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Invalid timeRange. Must be 7, 30, or 90.' } } } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Failed to fetch sentiment analysis data' }, message: { type: 'string', example: 'Internal server error' } } } } } } } } }, '/api/analytics/popular-posts': { get: { summary: '获取热门帖文数据', description: '返回按互动数量或互动率排序的热门帖文', tags: ['Analytics'], parameters: [ { name: 'timeRange', in: 'query', description: '时间范围(天)', schema: { type: 'string', enum: ['7', '30', '90'], default: '30' } }, { name: 'projectId', in: 'query', description: '项目ID', schema: { type: 'string' } }, { name: 'platform', in: 'query', description: '平台', schema: { type: 'string' } }, { name: 'sortBy', in: 'query', description: '排序字段', schema: { type: 'string', enum: ['engagement_count', 'engagement_rate'], default: 'engagement_count' } }, { name: 'limit', in: 'query', description: '返回数量限制', schema: { type: 'integer', default: 10, maximum: 50 } } ], responses: { '200': { description: '热门帖文数据', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'array', items: { type: 'object', properties: { title: { type: 'string' }, platform: { type: 'string' }, influencer_name: { type: 'string' }, publish_date: { type: 'string', format: 'date-time' }, engagement_count: { type: 'integer' }, views_count: { type: 'integer' }, engagement_rate: { type: 'number', format: 'float' }, is_high_engagement: { type: 'boolean' } } } }, metadata: { type: 'object', properties: { total: { type: 'integer' }, high_engagement_count: { type: 'integer' } } } } } } } }, '400': { description: '请求参数错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/analytics/moderation-status': { get: { summary: '获取内容审核状态分布', description: '返回已批准、待审核和已拒绝内容的数量和比例', tags: ['Analytics'], parameters: [ { name: 'timeRange', in: 'query', description: '时间范围(天)', schema: { type: 'string', enum: ['7', '30', '90'], default: '30' } }, { name: 'projectId', in: 'query', description: '项目ID', schema: { type: 'string' } }, { name: 'contentType', in: 'query', description: '内容类型', schema: { type: 'string', enum: ['post', 'comment', 'all'], default: 'all' } } ], responses: { '200': { description: '审核状态分布数据', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', properties: { statuses: { type: 'object', properties: { approved: { type: 'integer', description: '已批准内容数量' }, pending: { type: 'integer', description: '待审核内容数量' }, rejected: { type: 'integer', description: '已拒绝内容数量' } } }, percentages: { type: 'object', properties: { approved: { type: 'number', format: 'float', description: '已批准内容百分比' }, pending: { type: 'number', format: 'float', description: '待审核内容百分比' }, rejected: { type: 'number', format: 'float', description: '已拒绝内容百分比' } } }, total: { type: 'integer', description: '内容总数' } } } } } } } }, '400': { description: '请求参数错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/analytics/hot-keywords': { get: { summary: '获取热门关键词', description: '返回按出现频率排序的热门关键词列表,包含关键词、出现次数、占比和情感分数', tags: ['Analytics'], parameters: [ { name: 'timeRange', in: 'query', description: '时间范围(天)', schema: { type: 'integer', enum: [7, 30, 90], default: 30 } }, { name: 'projectId', in: 'query', description: '项目ID(可选)', schema: { type: 'string' } }, { name: 'platform', in: 'query', description: '平台(可选)', schema: { type: 'string', enum: ['weibo', 'xiaohongshu', 'douyin', 'bilibili'] } }, { name: 'limit', in: 'query', description: '返回关键词数量上限', schema: { type: 'integer', default: 20 } } ], responses: { '200': { description: '成功获取热门关键词', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: true }, data: { type: 'array', items: { type: 'object', properties: { keyword: { type: 'string', example: '质量' }, count: { type: 'integer', example: 38 }, percentage: { type: 'number', format: 'float', example: 19.0 }, sentiment_score: { type: 'number', format: 'float', example: 0.7 } } } }, metadata: { type: 'object', properties: { total: { type: 'integer', example: 100 }, is_mock_data: { type: 'boolean', example: false } } } } } } } }, '400': { description: '请求参数错误', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Invalid timeRange. Must be 7, 30, or 90.' } } } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Failed to fetch hot keywords data' } } } } } } } } }, '/api/analytics/interaction-time': { get: { summary: '获取用户互动时间分析', description: '返回按小时统计的用户互动数量和分布,帮助了解用户最活跃的时间段', tags: ['Analytics'], parameters: [ { name: 'timeRange', in: 'query', description: '时间范围(天)', schema: { type: 'integer', enum: [7, 30, 90], default: 30 } }, { name: 'projectId', in: 'query', description: '项目ID(可选)', schema: { type: 'string' } }, { name: 'platform', in: 'query', description: '平台(可选)', schema: { type: 'string', enum: ['weibo', 'xiaohongshu', 'douyin', 'bilibili'] } }, { name: 'eventType', in: 'query', description: '互动事件类型', schema: { type: 'string', enum: ['all', 'comment', 'like', 'view', 'share', 'follow'], default: 'all' } } ], responses: { '200': { description: '成功获取互动时间分析数据', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: true }, data: { type: 'array', items: { type: 'object', properties: { hour: { type: 'integer', example: 20 }, count: { type: 'integer', example: 256 }, percentage: { type: 'number', format: 'float', example: 15.2 } } } }, metadata: { type: 'object', properties: { total: { type: 'integer', example: 1680 }, peak_hour: { type: 'integer', example: 20 }, lowest_hour: { type: 'integer', example: 4 } } } } } } } }, '400': { description: '请求参数错误', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Invalid timeRange. Must be 7, 30, or 90.' } } } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean', example: false }, error: { type: 'string', example: 'Failed to fetch interaction time analysis data' } } } } } } } } }, '/api/analytics/content-performance': { get: { summary: '获取内容表现分析数据', description: '提供内容覆盖量、互动率、互动量等散点图数据,用于四象限分析', tags: ['Analytics'], parameters: [ { name: 'timeRange', in: 'query', description: '时间范围(天)', schema: { type: 'string', enum: ['7', '30', '90'], default: '30' } }, { name: 'projectId', in: 'query', description: '项目ID', schema: { type: 'string' } }, { name: 'platform', in: 'query', description: '平台', schema: { type: 'string' } }, { name: 'kolId', in: 'query', description: 'KOL ID', schema: { type: 'string' } }, { name: 'contentType', in: 'query', description: '内容类型', schema: { type: 'string', enum: ['post', 'video', 'article', 'all'], default: 'all' } }, { name: 'limit', in: 'query', description: '最大返回条数', schema: { type: 'integer', default: 100 } } ], responses: { '200': { description: '内容表现分析数据', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'array', items: { type: 'object', properties: { content_id: { type: 'string', description: '内容ID' }, title: { type: 'string', description: '内容标题' }, platform: { type: 'string', description: '平台' }, content_type: { type: 'string', description: '内容类型' }, influencer_name: { type: 'string', description: 'KOL名称' }, publish_date: { type: 'string', description: '发布日期' }, coverage: { type: 'integer', description: '内容覆盖量(阅读量/浏览量)' }, interaction_rate: { type: 'number', format: 'float', description: '互动率(互动总数/覆盖量)' }, interaction_count: { type: 'integer', description: '互动总量(点赞+评论+分享)' }, likes: { type: 'integer', description: '点赞数' }, comments: { type: 'integer', description: '评论数' }, shares: { type: 'integer', description: '分享数' }, quadrant: { type: 'string', enum: ['high_value', 'high_coverage', 'high_engagement', 'low_performance'], description: '四象限分类' } } } }, metadata: { type: 'object', properties: { total: { type: 'integer', description: '内容总数' }, average_coverage: { type: 'number', format: 'float', description: '平均覆盖量' }, average_interaction_rate: { type: 'number', format: 'float', description: '平均互动率' }, quadrant_counts: { type: 'object', properties: { high_value: { type: 'integer', description: '高价值内容数' }, high_coverage: { type: 'integer', description: '高覆盖内容数' }, high_engagement: { type: 'integer', description: '高互动内容数' }, low_performance: { type: 'integer', description: '低表现内容数' } } } } } } } } } }, '400': { description: '无效请求', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '500': { description: '服务器错误', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, }, 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 路由 - simplified configuration to fix white screen 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 }