1864 lines
75 KiB
JavaScript
1864 lines
75 KiB
JavaScript
"use strict";
|
||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||
};
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.createSwaggerUI = exports.openAPISpec = void 0;
|
||
const swagger_ui_1 = require("@hono/swagger-ui");
|
||
const hono_1 = require("hono");
|
||
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
||
const config_1 = __importDefault(require("../config"));
|
||
// 创建 OpenAPI 规范
|
||
exports.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'
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
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 路由
|
||
const createSwaggerUI = () => {
|
||
const app = new hono_1.Hono();
|
||
// 设置 Swagger UI 路由
|
||
app.get('/swagger', (0, swagger_ui_1.swaggerUI)({
|
||
url: '/api/swagger.json',
|
||
}));
|
||
// 提供 OpenAPI 规范的 JSON 端点
|
||
app.get('/api/swagger.json', (c) => {
|
||
return c.json(exports.openAPISpec);
|
||
});
|
||
// 添加临时的 token 生成端点,仅用于 Swagger 测试
|
||
app.get('/api/swagger/token', async (c) => {
|
||
try {
|
||
// 创建一个临时 token,与 authMiddleware 中的验证方式一致
|
||
const token = jsonwebtoken_1.default.sign({
|
||
sub: 'swagger-test-user',
|
||
email: 'swagger@test.com',
|
||
}, config_1.default.jwt.secret, {
|
||
expiresIn: '1h',
|
||
});
|
||
return c.json({
|
||
message: '此 token 仅用于 Swagger UI 测试',
|
||
token,
|
||
usage: '在 Authorize 对话框中输入: Bearer [token]'
|
||
});
|
||
}
|
||
catch (error) {
|
||
console.error('Error generating swagger token:', error);
|
||
return c.json({ error: 'Failed to generate token' }, 500);
|
||
}
|
||
});
|
||
return app;
|
||
};
|
||
exports.createSwaggerUI = createSwaggerUI;
|