3998 lines
116 KiB
TypeScript
3998 lines
116 KiB
TypeScript
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
|
||
}
|