Files
promote/backend/src/swagger/index.ts
2025-03-14 23:23:47 +08:00

3998 lines
116 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}