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' } } } } }, 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 }