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